2. Modelo inicial
Se plantea la siguiente primera alternativa para modelar el peso:
\(E(peso) = \beta_0 + \beta_1 * altura + \beta_2 * edad + \beta_3 * genero + \beta4 * diasActividadFisicaSemanal + \beta5 * consumoDiarioAlcohol\)
Primero se cargan las librerías necesarias:
options(warn=-1)
rm(list=ls())
gc()
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 3510005 187.5 5207762 278.2 5207762 278.2
Vcells 8173399 62.4 63074804 481.3 87948732 671.0
options(warn=-2)
# install.packages("pacman") -- Descomentar par instalar pacman
library(pacman)
p_load_gh('adrianmarino/commons')
import('../src/dataset.R')
[1] "-> '../src/dataset.R' script loadded successfuly!"
import('../src/preprocessing.R')
[1] "-> '../src/preprocessing.R' script loadded successfuly!"
import('../src/model.R')
[1] "-> '../src/model.R' script loadded successfuly!"
import('../src/plot.R')
[1] "-> '../src/plot.R' script loadded successfuly!"
A continuación se cargan los conjuntos de entrenamiento y test. también se resumen los valores de las variables categóricas y se excluyen los registros con missings, ya que son muy pocos en comparación de la cantidad total de resgístros.
train_set <- drop_missings(shorten_values(preprocess(load_train_set())))
test_set <- drop_missings(shorten_values(preprocess(load_test_set())))
glimpse(train_set)
Rows: 6,755
Columns: 15
$ edad <int> 17, 15, 15, 16, 17, 15, 13, 17, 17, 16, 16, 14, 15, 17, 15, 14, 17, 17, 16, 14, 1…
$ genero <fct> Femenino, Masculino, Masculino, Masculino, Masculino, Masculino, Femenino, Femeni…
$ nivel_educativo <ord> 2, 1, 2, 1, 2, 1, 9, 9, 1, 3, 3, 8, 9, 3, 9, 2, 3, 3, 2, 9, 8, 2, 3, 2, 2, 3, 1, …
$ altura <int> 165, 178, 172, 170, 170, 178, 156, 163, 164, 167, 185, 146, 180, 175, 183, 165, 1…
$ peso <int> 62, 62, 62, 65, 75, 88, 46, 60, 57, 51, 100, 33, 62, 70, 80, 60, 50, 50, 70, 75, …
$ frecuencia_hambre_mensual <ord> Rara vez, Rara vez, Nunca, Nunca, Rara vez, Nunca, Nunca, Nunca, Nunca, Nunca, Nu…
$ dias_consumo_comida_rapida <int> 0, 0, 3, 1, 1, 2, 0, 0, 0, 3, 4, 2, 1, 1, 3, 0, 0, 0, 1, 0, 6, 0, 1, 0, 2, 0, 2, …
$ edad_consumo_alcohol <ord> 14-15, <=7, 0, 14-15, 16-17, 8-9, 10-11, 16-17, <=7, 0, 12-13, 12-13, 0, 14-15, <…
$ consumo_diario_alcohol <dbl> 5.0, 4.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.5, 5.0, 0.0, 5.0, 0.0, 0.0, 2.0, 1.0, 0.0, 0…
$ dias_actividad_fisica_semanal <int> 7, 7, 7, 7, 0, 7, 0, 2, 7, 3, 2, 2, 7, 1, 4, 0, 6, 5, 7, 3, 0, 7, 5, 2, 2, 4, 2, …
$ consumo_semanal_frutas <ord> 0, 0, 0, 4-6, 14, 7, 14, 21, 0, 14, <=3, <=3, 7, <=3, <=3, <=3, <=3, <=3, 14, <=3…
$ consumo_semanal_verdura <ord> 4-6, 4-6, 7, >=28, <=3, 14, 4-6, 7, 0, 4-6, <=3, 7, 7, <=3, 4-6, <=3, <=3, 4-6, <…
$ consumo_semanal_gaseosas <ord> <=3, <=3, 4-6, <=3, 7, 4-6, 0, 7, <=3, 4-6, 4-6, <=3, <=3, 4-6, 4-6, <=3, 0, 0, 7…
$ consumo_semanal_snacks <ord> <=3, 0, 4-6, <=3, 0, 4-6, 0, <=3, 0, <=3, <=3, 0, <=3, 7, <=3, 0, <=3, <=3, <=3, …
$ consumo_semanal_comida_grasa <ord> 0, 4-6, 0, 0, <=3, 4-6, <=3, 7, 0, <=3, 0, <=3, 0, 7, 0, 4-6, 0, <=3, <=3, <=3, <…
Se fija la semilla y se validan las proporciones de los conjuntos de entrenamiento y test:
set.seed(25)
show_train_test_props(train_set, test_set)
[1] "Train: 70%, Test: 30%"
Modelo 1
Se plantea el primer modelo lineal:
model_1 <- lm(
peso ~ altura + edad + genero + dias_actividad_fisica_semanal + consumo_diario_alcohol,
data = train_set
)
¿Cuál es la interpretación de cada uno de los coeficientes estimados?
Veamos a continuación un resumen de los coeficiente del primer modelo:
coefficients_summary(model_1)

Al analizar cada coeficiente se encuentra que:
\(\hat{\beta_0}\) (Ordenada al origen) de valor -72.72 Kg, es el peso esperado o promedio de un individuo que tiene cero altura, edad, actividad física y consumo diario de alcohol (Medido en tragos). Esto no es interpretable, ya que miniamente una persona tiene que tener una altura superior a cero y no puede tener un peso negativo, pero si podría no realizar actividad física ni consumir alcohol.
El coeficiente \(\hat{\beta_1}\) de valor 680 gramos, corresponde a la altura del individuo. Este coeficiente indica que dada una edad, consumo de alcohol diario (Medido en tragos) y días de actividad física semanal fijos, cada incremento en 1 cm adicional en la altura del individuo implica un aumento de su peso esperado o promedio de 680 gramos.
El coeficiente \(\hat{\beta_2}\) de valor 1.33 Kg, corresponde a la edad del individuo. Este coeficiente indica que dada una altura, días de actividad física y consumo de alcohol diario fijos, cada vez que el individuo cumple año su peso esperado o promedio aumenta en 1.33 kg.
El coeficiente \(\hat{\beta_3}\) de valor 77.59 gramos, corresponde a los días de actividad física semanal que realiza el individuo. Este coeficiente indica que dada una altura, edad y consumo de alcohol diario (Medido en tragos) fijos, cada vez que un individuo realiza un día mas de actividad física semanal su peso esperado o promedio disminuye en 77.59 gramos.
El coeficiente \(\hat{\beta_4}\) de valor -8 gramos, corresponde al nivel de consumo diario de alcohol del individuo. Este coeficiente indica que dada una altura, una edad y días de actividad física semanal fijos, cada vez que el individuo consume un trago de alcohol su peso esperado o promedio disminuye en 8 gramos. A simple vista podrá no llegar a tener sentido, ya que a mayor consumo de alcohol el peso debería aumentar, ya sea por el peso del propio liquido como el peso equivalente en grasas.
¿Son significativos los coeficientes?
Para determina si los coeficientes son aptos para explicar el peso de un individuo se realiza un \({T}\) test para cada coeficiente en el cual se evalúan las siguientes hipótesis:
- \({H_0: \beta_i = 0}\)
- \({H_1: \beta_i \neq 0}\)
Si \({\beta_i \neq 0}\) podemos decir que existe una diferencia estadisticamente significativas del coeficiente \({\beta_i}\) del valor cero, y por lo tanto el coeficiente \({\beta_i}\) explicar la variable \({y}\) (Peso en nuestro caso).
Luego analizando la salida de coefficients_summary concluimos que:
- Los coeficientes correspondientes de la altura(\({\beta_1}\)) y edad(\({\beta_2}\)) tienen un p-valor < 0.05. Por lo tanto se rechaza la hipótesis nula y ambos resultan estadistitamente significativas para explicar el peso. Por otro lado, se puede apreciar que los intervalos de confianza (del 95%) de ambos coeficientes no incluyen al cero.
- Lo contrario sucede con días de actividad física semanal(\({\beta_3}\)) y consumo de alcohol diario (\({\beta_4}\)), dato que ambos no rechazar la hipótesis nula (p-valor > 0.05) y por lo tanto no existe una diferencia significativa del cero. Finalemnte, no hay evidencia estadistitamente significativas para explicar el peso.
¿El modelo resulta significativo para explicar el peso?
glance(model_1)
Para determinar si es modelo es significativo para explicar el peso de un individuo se realiza un \(F\) test con las siguientes hipótesis:
- \(H_0: β_1 = β_2 = · · · = β_{p−1} = 0\)
- \(H_1:\) Por lo menos un \(β_k\) (\(k = 1, 2,..., p−1\)) es distinto de 0.
\(H_0\) afirma que no hay vinculo entre la variable \({y}\)(Peso) y las variables regresoras. \(H_1\) afirma que al menos una de las variables regresoras sirve para predecir la variable \({y}\) (Peso).
Veamos los resultados de \(F\) test:
glance(model_1)
Dado que el p-valor < 0.05 e igual a 0, con mucha certeza podemos decir que al menos una de las variables regresoras permite explicar el peso. Esto concuerda con los resultados de los \(T\) test para las los coeficientes correspondientes a Altura y Edad.
¿Qué porcentaje de la variabilidad explica el modelo?
Según el valor de \(R^2\) ajustado (adj.r.squared), este modelo llega a explica el 35% de la variabilidad del dataset de entrenamiento, lo cual no es un valor bajo pero tampoco es despreciable.
3. Modelo categóricas
Se sugiere probar un modelo que incorpore el consumo semanal de snacks y una interacción entre el género y la edad, en lugar de actividad física y consumo de alcohol. Además se pide explicitamente que la categoría “No comí comida salada o snacks en los últimos 7 días” de la variable consumo_semanal_snacks se encuentre como nivel/categoría basal.
\(E(peso) = \beta_0 + \beta_1 * altura + \beta_2 * edad + + \beta_3 * genero + \beta4 * consumoSemanalSnacks + \beta_5 * genero * edad\)
Primero definimos cual es la primera categoría en cada factor, ya que esta sera la que el modelo defina como categoría basal:
train_set$consumo_semanal_snacks <- factor(
train_set$consumo_semanal_snacks,
levels=c("0","<=3", "4-6", "7", "14", "21", ">=28"),
ordered=FALSE
)
table(train_set$consumo_semanal_snacks)
0 <=3 4-6 7 14 21 >=28
2090 3044 599 580 223 96 123
Se puede apreciar que la primera categoría corrspondeo a 0 consumo de snacks semanal.
table(train_set$genero)
Femenino Masculino
3655 3100
Por otro lado la categoría genero esta correstamente balanceada.
Modelo 2
Definimos el nuevo modelo:
model_2 <- lm(
peso ~ altura + edad + genero + consumo_semanal_snacks + genero * edad,
data = train_set
)
¿Cuál es la interpretación de los coeficientes estimados para las categorías de consumo_semanal_snacks y genero * edad? ¿Son significativas?
coefficients_summary(model_2)

Si interpretamos los coeficientes que son significativos:
La altura y la edad siguen siendo significativos al igual que el modelo anterior, con la diferencia que cambiaron ligeramente sus valores a 0.64 y 1.21.
Para una altura, edad fijos, la diferencia de peso de los individuos que consumen snacks hasta 3 veces por semana de los que no consumen(Categoría basal) es de -1.436 kg. Esto a simple vista pareciera no tener sentido, ya que quienes consumen snacks hasta 3 veces por semana deberían tener un peso menor. Lo mismo sucede con las categorías consumo_semanal_snacks 4-6 y >=28.
¿Qué porcentaje de la variabilidad explica el modelo? En caso de detectar que existen categorías no significativas de la variable consumo_semanal_snacks evaluar si la variable es significativa en su conjunto y, en caso afirmativo, proponer una redefinición de las mismas que permita obtener una mayor proporción de categorías significativas individualmente. Luego, analizar si existen cambios en la variabilidad explicada por el modelo.
Viendo el resultado de coefficients_summary se aprecia que las siguientes categorías de consumo_semanal_snacks no son significativas:
- 2 veces al día (14 veces/semana)
- 4 a 6 veces durante los últimos 7 (4 a 6 veces semana)
- 3 veces al día (21 veces/semana)
Pero si son significativas los extremos:
De 1 a 3 veces/semana
De 28 o mas veces/semana
A continuación se realiza un \(F\) test para evaluar la significatividad conjunta de las categóricas de la variable consumo_semanal_snacks para explicar el peso.
El \(F\) test también llamando ANOVA (Análisis de la variarían) se realiza para probar la significatividad conjunta de todos los valores de una variable categórica.
Las hipótesis son las siguientes:
Luego si todosl los coeficientes asociados a los valores de variable categórica son cero, se rechaza la hipótesis nula y por lo tanto la variable no es significartiva para explicar el peso en nuestro caso.
A continuación veremos el p-valor resultado de aplicar \(F\) test para cada variable del modelo:
anova_summary(model_2)
Podemos apreciar que el p-value < 0.005 para la variable consumo_semanal_snacks. Por lo tanto se rechaza la hipótesis nula y podemos decir en su conjunto resulta estadísticamente significativa para explicar el peso. Luego, como la variable consumo_semanal_snacks es significativa vale la pena re-definirla.
Por otro lado, la combinación de variables genero-edad no es estadísticamente significativa para explicar el peso, pero si lo es el genero en forma separada. Finalmente, como ya vimos en pasis anteriores, edad y altura son significativas.
Veamos a continuación las distribuciones de las categorías de la variable consumo_semanal_snacks ordenadas por la mediana del peso:
segmented_box_plot(
train_set,
column = 'peso',
segmented_by = 'consumo_semanal_snacks',
title = 'Consumo de snacks ordenado por la mediana del peso',
y_label = 'Peso (Kg)',
y_limits = c(10, 130),
x_label = 'Consumo de snacks (Veces/Semana)'
)

A simple vista no parece haber una gran diferencia, pero si se aprecia que los extremos difieren del los valores centrales.
A continua con se promane una nueva definición de la variable consumo_semanal_snacks. Primero se realiza el promediodel peso para cada categoría de la variable consumo_semanal_snacks:
peso_medio_by_nivel_consumo_snack = train_set %>%
group_by(consumo_semanal_snacks) %>%
summarise(promedio = mean(peso))
ggplot(data = peso_medio_by_nivel_consumo_snack, aes(x = promedio)) +
geom_boxplot(alpha = 0.75, fill="blue") +
labs(title = "Peso promedio por cada categoria de consumo de snacks") +
labs(x = "Peso medio") +
theme_bw()

peso_medio_by_nivel_consumo_snack <- peso_medio_by_nivel_consumo_snack$promedio
peso_medio_by_nivel_consumo_snack
[1] 60.68182 59.13633 58.29215 58.29483 58.69507 58.27083 55.52033
quantile(peso_medio_by_nivel_consumo_snack)
0% 25% 50% 75% 100%
55.52033 58.28149 58.29483 58.91570 60.68182
Se puede apreciar que es una distribución asimétrica sesgada a derecha, ya que los mayores valores se encueran arriba del segundo cuantil (Mediana).
A continuación se re-definen las categorías originales por 3 nueva categorías: Bajo, Medio, Alto. Esta categorías estan asociadas al peso de de individuo. Si el indivídio tiene un peso menor al Q1, se le asigna el nivel Bajo, si esta entre Q1 y Q3 sera Medio y Alta arroba de Q3. Finalmente a continuación se transforma la variable en el conjunto de entrenamiento y en test se usas los los cuantíles generados con en conjunto de entrenamiento:
q1 <- quantile(peso_medio_by_nivel_consumo_snack)[2]
q3 <- quantile(peso_medio_by_nivel_consumo_snack)[4]
train_set2 <- train_set %>%
mutate(consumo_semanal_snacks = case_when(peso < q1 ~ "Bajo", peso >= q3 ~ "Alto", TRUE ~ "Medio"))
test_set2 <- test_set %>%
mutate(consumo_semanal_snacks = case_when(peso < q1 ~ "Bajo", peso >= q3 ~ "Alto", TRUE ~ "Medio")) %>%
mutate(consumo_semanal_snacks = as.factor(consumo_semanal_snacks))
test_set2 %>% segmented_box_plot(
column = 'peso',
segmented_by = 'consumo_semanal_snacks',
title = 'Consumo de snacks ordenado por la mediana del peso en Test',
y_label = 'Peso (Kg)',
y_limits = c(40, 100),
x_label = 'Consumo de snacks (Veces/Semana)'
)

Se puede apreciar que en el conjunto de test no hay valores medio, pero existe en el conjunto de entrenamiento.
Modelo 3
A continuación definimos un nuevo modelo igual al anterior pero ahora ya usando la re-definición de la variable consumo_semanal_snacks:
model_3 <- lm(
peso ~ altura + edad + genero + consumo_semanal_snacks + genero * edad,
data = train_set2
)
models <- list('Modelo 1'=model_1, 'Modelo 2'=model_2, 'Modelo 3'=model_3)
coefficients_summary(model_3)

anova_summary(model_3)
models %>%
map_df(glance, .id = "model") %>%
arrange(desc(adj.r.squared))
Finalmente se aprecia que la nueva categorización de la variable aumenta el \(R^2\) Ajustado a casi el doble (Del 35.7 al 64.7%). Si bien esto mejora la capacidad explicativa del modelo, en pasos posteriores se deberá determina si produce o no overfitting sobre el conjunto de entrenamiento.
4. Modelos propios y evaluación
Realizar 2 modelos lineales múltiples adicionales y explicar breve-mente la lógica detrás de los mismos (se valorará la creación y/o inclusión de variables nuevas).
Evaluar la performance del modelo inicial, el modelo categóricas con las categorías redefinidas de la variable consumo_semanal_snacks y los modelos desarrollados en este punto en el dataset de entrenamiento y evaluación (usar dataset “encuesta_salud_test.csv”).
La evaluación de performance consiste en comparar en ambos sets la performance en términos del R cuadrado ajustado, RMSE y MAE.
Al continuación se define 2 modelos.
Modelo 4
\(E(peso) = \beta_0 + \beta_1 * altura + \beta_2 * edad + + \beta_3 * genero + \beta4 * consumoSemanalSnacks + \beta_5 * diasActividadFisicaSemanal + \beta_6 * altura * genero\)
Se utilizo la redefinición de la variable consumo_semanal_snacks como base. Ademase se agregar la variable dias_actividad_fisica_semanal entendiendo que tiene una influencia iportante en el peso y luego la asociacion altura * genero ya que en general mas mujeres tienen a ser mas bajar que los varones y vise versa.
model_4 <- lm(
peso~
altura +
edad +
genero +
consumo_semanal_snacks +
dias_actividad_fisica_semanal +
altura*genero,
data = train_set2
)
coefficients_summary(model_4)

anova_summary(model_4)
glance(model_4)
train_set3 <- column_mean_quantile_binning(train_set2, 'dias_actividad_fisica_semanal')
test_set3 <- column_mean_quantile_binning(test_set2, 'dias_actividad_fisica_semanal')
train_set3 <- column_mean_quantile_binning(train_set3, 'consumo_semanal_frutas')
test_set3 <- column_mean_quantile_binning(test_set3, 'consumo_semanal_frutas')
train_set3 <- column_mean_quantile_binning(train_set3, 'consumo_semanal_verdura')
test_set3 <- column_mean_quantile_binning(test_set3, 'consumo_semanal_verdura')
train_set3 <- column_mean_quantile_binning(train_set3, 'consumo_semanal_comida_grasa')
test_set3 <- column_mean_quantile_binning(test_set3, 'consumo_semanal_comida_grasa')
train_set3 <- column_mean_quantile_binning(train_set3, 'consumo_semanal_gaseosas')
test_set3 <- column_mean_quantile_binning(test_set3, 'consumo_semanal_gaseosas')
segmented_box_plot(
test_set3,
column = 'peso',
segmented_by = 'dias_actividad_fisica_semanal',
title = 'Niveles actividad fisica ordenados por la mediana del peso en Test',
y_label = 'Peso (Kg)',
y_limits = c(40, 100),
x_label = 'Niveles de actividad física (Dias)'
)

Modelo 5
\(E(peso) = \beta_0 + \beta_1 * altura + \beta_2 * edad + + \beta_3 * genero + \beta4 * consumoSemanalSnacks + \beta_5 * diasActividadFisicaSemanal + \beta_6 * consumoSemanalFrutas + \beta_7 * consumoSemanalVerduras + \beta_8 * consumoSemanalGrasas + \beta_9 * consumoSemanalGaseosas\)
Se utilizo la redefinición de la variable consumo_semanal_snacks como base. Ademase se agregar la variable consumo_semenal_frutras/verduras/grasas/gaseaosas entendiendo que también tiene una influencia importante en el peso.
model_5 <- lm(
peso ~
edad +
genero +
altura +
consumo_semanal_snacks +
consumo_semanal_frutas +
consumo_semanal_verdura,
data = train_set3
)
coefficients_summary(model_5)

anova_summary(model_5)
glance(model_5)
c(models, list('Modelo 4'=model_4, 'Modelo 5'=model_5)) %>%
map_df(glance, .id = "model") %>%
arrange(desc(adj.r.squared))
Finalmente, si comparamos los modelos por \(R^2\) Ajustado, se puede apreciar que el modelo 5 (con todas las variables categóricas re-definidas) llega a captar la mayor varianza explicada sobre el dataset de entrenamiento. Por supuesto esto no dice nada acerca de la performance del modelo en test, pero si que tiene la mejor capacidad para extraer información de los dato de entrenamiento.
¿Cuál es el mejor modelo para nuestro objetivo de predecir el peso? ¿Por qué?
Ahora comparamos la performance de todo los modelos al evaluar el error delos mismo al predecir el peso en el conjunto de train y test tanto para RMSE como MAE:
RMSE
custom_models_evaluation_summary(
model_1, model_2, model_3, model_4, model_5,
test_set, test_set2, test_set3,
metric_fn = rmse
)
Si utilizamos la métrica RMSE podemos ver que el modelo 5 tiene el menor error en el conjunto de test. Por otro lados el que tiene la mayor diferencia de error entre test y entrenamiento. Esto nos dice que podría estar sobre-ajustandose al conjunto de entrenamiento. El modelo 3 tiene un error en test muy cercano y ademas tiene un diferencia entre test y train mucho menor. por esto ultimo parece ser el mejor modelo ya que tiene prácticamente el menor error posible y también el menor sobre-ajuste al conjunto de entrenamiento.
MAE
custom_models_evaluation_summary(
model_1, model_2, model_3, model_4, model_5,
test_set, test_set2, test_set3,
metric_fn = mae
)
Si medimos a partir del MAE sucede algo muy similar, El modelo 3 es es que tiene menor error y ademas menos sobre-ajuste.
Finalmente, según ambas metricas el moejor modelo es el Modelo 3.
5. Diagnóstico del modelo
Analizar en profundidad el cumplimiento de los supuestos del modelo lineal para el modelo inicial.
plot(model_1)




Homosedastisidad
Al visualizar el primer gráfico (Residuos vs. Valores ajustados) se puede apreciar que no hay presencia de homocedastrisisdad, ya que los valores predicho, la variabilidad o amplitud de los residuos parece mantenerse con cierta regularidad. Dadas esta condiciones podemos decir que se cumple el supuesto de varianza constante.
Normalidad
Al visualizar el diagrama QQ-Plot podemos observas que en el extremo derecha, el modelo sobre estima el peso del los individuos ya que hay una gran diferencia positiva entre el valor predicho y el valor esperado teórico. lo mis sucede a izquierca pero en menor medida, donde el modelo subestima el valor de peso en comparación al valor esperado teórico. Como dato adiciona este grafito corresponde a una distribución sesgada a derecha, también conocido como sesgo positivo. Finalmente el QQ-Plot no muestra un grado de alejamiento pronunciado de una districion normal teórica y decimos que no se cumple el supuesto de normalidad del modelo.
Apalancamiento (Leverage)
Si observamos el gráfico de Residuos vs Apalacamiento vemos que varias observaciones o individuos que se alejan del cumulo de principal. Estos ejercen un apuntalamiento sobre el valores predicho del modelo a partir de un apalancamiento(leverage) 0.0020 y es mas pronunciado desde 0.0025. Finalmente vemos un grado importante de desvió de las predicciones vs su vor esperado.
A continuación se pueden ver lo individuos que producen mayor apalancamiento(leverage) y por ende sesgo en al predicción del modelo:
augment(model_1) %>%
filter(.hat>0.00245) %>%
arrange(.hat)
6. Modelo Robusto
Leer el archivo “encuesta_salud_modelo6.csv”. Este último consiste en el dataset original de train con la incorporación de algunas observaciones adicionales que pueden incluir valores atípicos. En particular, observar la relación entre peso y altura ¿Qué ocurre con estos nuevos datos? Entrenar el modelo inicial con estos nuevos datos y comentar qué se observa en los coeficientes estimados y las métricas de evaluación (R cuadrado ajustado, RMSE y MAE) respecto al modelo entrenado con el set de entrenamiento original. Entrenar un modelo robusto con la misma especificación que el modelo inicial sobre los nuevos datos. Comparar los coeficientes y su performance (RMSE y MAE) respecto al modelo inicial no robusto entrenado en este punto. ¿Qué puede concluir al respecto?
Se carga el conjunto de entrenamiento en crudo,e s decir sin pre-procesamiento. Luego se resumen los valores de las variables categóricas y se eliminan missing values, ya que siguen siendo muy poco casos:
original_train_set <- shorten_values(preprocess(load_original_train_set()))
missings_summary(original_train_set)
new_train_set <- drop_missings(original_train_set)
missings_summary(new_train_set)
nrow(original_train_set)
[1] 7060
nrow(new_train_set)
[1] 6789
Comparemos las distribuciones del peso vs altura en ambos conjunto de entrenamiento:
box_plots(
train_set %>% select(peso, altura),
title = 'Comparativas de distribuciones del peso y la altura'
)

box_plots(
new_train_set %>% select(peso, altura),
title = 'Comparativas de distribuciones del peso y la altura'
)

En el dataset de entrenamiento original la variable peso tiene prácticamente el doble de outliers que el dataset procesado.
Modelo 6
Definimos un modelo igual al modelo 1 pero entrenando en el dataset de entrenamiento original.
model_6 <- lm(
peso ~ altura + edad + dias_actividad_fisica_semanal + consumo_diario_alcohol,
data = new_train_set
)
coefficients_summary(model_6)

anova_summary(model_6)
glance(model_6)
print(paste('Disminicion de adj.r.squared:', abs(0.352113 - 0.2734821) * 100, '%'))
[1] "Disminicion de adj.r.squared: 7.86309 %"
Dada la presencia de outliers en la variable peso, el \(R^2\) Ajustado baja con respecto al modelo 1.
models <- list('Modelo 6'=model_6)
models_evaluation_summary(models, train_set, metric_fn = rmse)
models_evaluation_summary(models, train_set, metric_fn = mae)
Por otro lado, aumento el error de predicción tanto en train como en test. Finalmente, el modelo tiene un grado de overfitting mucho mayor que los modelos anteriores, ya que la métrica de evaluación en test y train tiene una diferencia muy pronunciada de 1.7 puntos.
Modelo 7
Definimos un modelo igual al modelo 1 entrenando en el dataset de entrenamiento original y usamos un modelo lineal robusto.
p_load(MASS)
model_7 <- rlm(
peso ~ altura + edad + dias_actividad_fisica_semanal + consumo_diario_alcohol,
data = new_train_set
)
coefficients_summary(model_7)
[1] "WARN: p.value column is required!\n"
[1] "WARN: p.value column is required!\n"
NULL
anova_summary(model_7)
models <- list('Modelo 6'=model_6, 'Modelo 7'=model_7)
models_evaluation_summary(models, test_set, metric_fn = rmse)
models_evaluation_summary(models, test_set, metric_fn = mae)
El modelo lineal robusto (Modelo 7) parece tener un menor error de entrenamiento muy cercano al modelo 6, pero tiene mayor sobre- ajuste que el modelo 6, aunque es una diferencia muy baja.
Dado esto, seria una buena selecciono elegir el modelo 7, ya que el sobre ajuste practicamente no cambia y obtenemos un error de predicción en test ligeramente menor.
LS0tCnRpdGxlOiB8CiAgICB8IE1hZXN0csOtYSBlbiBFeHBsb3RhY2nDs24gZGUgZGF0b3MgeSBEZXNjdWJyaW1pZW50byBkZSBjb25vY2ltaWVudG8KICAgIHwKICAgIHwKICAgIHwgTWF0ZXJpYTogRW5mb3F1ZSBFc3RhZGlzdGljbyBkZWwgQXByZW5kaXphamUKICAgIHwgVHJhYmFqbyBwcsOhY3RpY28gMTogUmVncmVzaW9uIExpbmVhbAphdXRob3I6ICJBZHJpYW4gTm9yYmVydG8gTWFyaW5vIgpkYXRlOiAiMjAyMS8wOS8xOSIKZmlnX3dpZHRoOiAzIApmaWdfaGVpZ2h0OiAzIApvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRoZW1lOiBzYW5kc3RvbmUKICAgIHRvYzogeWVzCiAgICBkZl9wcmludDogcGFnZWQKICAgIGluY2x1ZGVzOgogICAgICBiZWZvcmVfYm9keTogLi9oZWFkZXIuaHRtbAogICAgICBhZnRlcl9ib2R5OiAuL2Zvb3Rlci5odG1sCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCmVkaXRvcl9vcHRpb25zOiAKICBtYXJrZG93bjogCiAgICB3cmFwOiAxMDAKLS0tCgojIyAyLiBNb2RlbG8gaW5pY2lhbAoKU2UgcGxhbnRlYSBsYSBzaWd1aWVudGUgcHJpbWVyYSBhbHRlcm5hdGl2YSBwYXJhIG1vZGVsYXIgZWwgcGVzbzoKCiRFKHBlc28pID0gXGJldGFfMCArIFxiZXRhXzEgKiBhbHR1cmEgKyBcYmV0YV8yICogZWRhZCArIFxiZXRhXzMgKiBnZW5lcm8gKyBcYmV0YTQgKiBkaWFzQWN0aXZpZGFkRmlzaWNhU2VtYW5hbCArIFxiZXRhNSAqIGNvbnN1bW9EaWFyaW9BbGNvaG9sJAoKUHJpbWVybyBzZSBjYXJnYW4gbGFzIGxpYnJlcsOtYXMgbmVjZXNhcmlhczoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm9wdGlvbnMod2Fybj0tMSkKcm0obGlzdD1scygpKQpnYygpCm9wdGlvbnMod2Fybj0tMikKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpIC0tIERlc2NvbWVudGFyIHBhciBpbnN0YWxhciBwYWNtYW4KbGlicmFyeShwYWNtYW4pCnBfbG9hZF9naCgnYWRyaWFubWFyaW5vL2NvbW1vbnMnKQppbXBvcnQoJy4uL3NyYy9kYXRhc2V0LlInKQppbXBvcnQoJy4uL3NyYy9wcmVwcm9jZXNzaW5nLlInKQppbXBvcnQoJy4uL3NyYy9tb2RlbC5SJykKaW1wb3J0KCcuLi9zcmMvcGxvdC5SJykKYGBgCgpBIGNvbnRpbnVhY2nDs24gc2UgY2FyZ2FuIGxvcyBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byB5IHRlc3QuIHRhbWJpw6luIHNlIHJlc3VtZW4gbG9zIHZhbG9yZXMgZGUKbGFzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgeSBzZSBleGNsdXllbiBsb3MgcmVnaXN0cm9zIGNvbiBtaXNzaW5ncywgeWEgcXVlIHNvbiBtdXkgcG9jb3MgZW4KY29tcGFyYWNpw7NuIGRlIGxhIGNhbnRpZGFkIHRvdGFsIGRlIHJlc2fDrXN0cm9zLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdHJhaW5fc2V0IDwtIGRyb3BfbWlzc2luZ3Moc2hvcnRlbl92YWx1ZXMocHJlcHJvY2Vzcyhsb2FkX3RyYWluX3NldCgpKSkpCnRlc3Rfc2V0ICA8LSBkcm9wX21pc3NpbmdzKHNob3J0ZW5fdmFsdWVzKHByZXByb2Nlc3MobG9hZF90ZXN0X3NldCgpKSkpCmBgYAoKYGBge3J9CmdsaW1wc2UodHJhaW5fc2V0KQpgYGAKClNlIGZpamEgbGEgc2VtaWxsYSB5IHNlIHZhbGlkYW4gbGFzIHByb3BvcmNpb25lcyBkZSBsb3MgY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8geSB0ZXN0OgoKYGBge3J9CnNldC5zZWVkKDI1KQpzaG93X3RyYWluX3Rlc3RfcHJvcHModHJhaW5fc2V0LCB0ZXN0X3NldCkKYGBgCgoqKk1vZGVsbyAxKioKClNlIHBsYW50ZWEgZWwgcHJpbWVyIG1vZGVsbyBsaW5lYWw6CgpgYGB7cn0KbW9kZWxfMSA8LSBsbSgKICBwZXNvIH4gYWx0dXJhICsgZWRhZCArIGdlbmVybyArIGRpYXNfYWN0aXZpZGFkX2Zpc2ljYV9zZW1hbmFsICsgY29uc3Vtb19kaWFyaW9fYWxjb2hvbCwgCiAgZGF0YSA9IHRyYWluX3NldAopCmBgYAoKKirCv0N1w6FsIGVzIGxhIGludGVycHJldGFjacOzbiBkZSBjYWRhIHVubyBkZSBsb3MgY29lZmljaWVudGVzIGVzdGltYWRvcz8qKgoKVmVhbW9zIGEgY29udGludWFjacOzbiB1biByZXN1bWVuIGRlIGxvcyBjb2VmaWNpZW50ZSBkZWwgcHJpbWVyIG1vZGVsbzoKCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTYsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KY29lZmZpY2llbnRzX3N1bW1hcnkobW9kZWxfMSkKYGBgCgpBbCBhbmFsaXphciBjYWRhIGNvZWZpY2llbnRlIHNlIGVuY3VlbnRyYSBxdWU6CgotICAgJFxoYXR7XGJldGFfMH0kIChPcmRlbmFkYSBhbCBvcmlnZW4pIGRlIHZhbG9yIC03Mi43MiBLZywgZXMgZWwgcGVzbyBlc3BlcmFkbyBvIHByb21lZGlvIGRlIHVuCiAgICBpbmRpdmlkdW8gcXVlIHRpZW5lIGNlcm8gYWx0dXJhLCBlZGFkLCBhY3RpdmlkYWQgZsOtc2ljYSB5IGNvbnN1bW8gZGlhcmlvIGRlIGFsY29ob2wgKE1lZGlkbyBlbgogICAgdHJhZ29zKS4gRXN0byBubyBlcyBpbnRlcnByZXRhYmxlLCB5YSBxdWUgbWluaWFtZW50ZSB1bmEgcGVyc29uYSB0aWVuZSBxdWUgdGVuZXIgdW5hIGFsdHVyYQogICAgc3VwZXJpb3IgYSBjZXJvIHkgbm8gcHVlZGUgdGVuZXIgdW4gcGVzbyBuZWdhdGl2bywgcGVybyBzaSBwb2Ryw61hIG5vIHJlYWxpemFyIGFjdGl2aWRhZCBmw61zaWNhCiAgICBuaSBjb25zdW1pciBhbGNvaG9sLgoKLSAgIEVsIGNvZWZpY2llbnRlICRcaGF0e1xiZXRhXzF9JCBkZSB2YWxvciA2ODAgZ3JhbW9zLCBjb3JyZXNwb25kZSBhIGxhIGFsdHVyYSBkZWwgaW5kaXZpZHVvLiBFc3RlCiAgICBjb2VmaWNpZW50ZSBpbmRpY2EgcXVlIGRhZGEgdW5hIGVkYWQsIGNvbnN1bW8gZGUgYWxjb2hvbCBkaWFyaW8gKE1lZGlkbyBlbiB0cmFnb3MpIHkgZMOtYXMgZGUKICAgIGFjdGl2aWRhZCBmw61zaWNhIHNlbWFuYWwgZmlqb3MsIGNhZGEgaW5jcmVtZW50byBlbiAxIGNtIGFkaWNpb25hbCBlbiBsYSBhbHR1cmEgZGVsIGluZGl2aWR1bwogICAgaW1wbGljYSB1biBhdW1lbnRvIGRlIHN1IHBlc28gZXNwZXJhZG8gbyBwcm9tZWRpbyBkZSA2ODAgZ3JhbW9zLgoKLSAgIEVsIGNvZWZpY2llbnRlICRcaGF0e1xiZXRhXzJ9JCBkZSB2YWxvciAxLjMzIEtnLCBjb3JyZXNwb25kZSBhIGxhIGVkYWQgZGVsIGluZGl2aWR1by4gRXN0ZQogICAgY29lZmljaWVudGUgaW5kaWNhIHF1ZSBkYWRhIHVuYSBhbHR1cmEsIGTDrWFzIGRlIGFjdGl2aWRhZCBmw61zaWNhIHkgY29uc3VtbyBkZSBhbGNvaG9sIGRpYXJpbwogICAgZmlqb3MsIGNhZGEgdmV6IHF1ZSBlbCBpbmRpdmlkdW8gY3VtcGxlIGHDsW8gc3UgcGVzbyBlc3BlcmFkbyBvIHByb21lZGlvIGF1bWVudGEgZW4gMS4zMyBrZy4KCi0gICBFbCBjb2VmaWNpZW50ZSAkXGhhdHtcYmV0YV8zfSQgZGUgdmFsb3IgNzcuNTkgZ3JhbW9zLCBjb3JyZXNwb25kZSBhIGxvcyBkw61hcyBkZSBhY3RpdmlkYWQgZsOtc2ljYQogICAgc2VtYW5hbCBxdWUgcmVhbGl6YSBlbCBpbmRpdmlkdW8uIEVzdGUgY29lZmljaWVudGUgaW5kaWNhIHF1ZSBkYWRhIHVuYSBhbHR1cmEsIGVkYWQgeSBjb25zdW1vIGRlCiAgICBhbGNvaG9sIGRpYXJpbyAoTWVkaWRvIGVuIHRyYWdvcykgZmlqb3MsIGNhZGEgdmV6IHF1ZSB1biBpbmRpdmlkdW8gcmVhbGl6YSB1biBkw61hIG1hcyBkZQogICAgYWN0aXZpZGFkIGbDrXNpY2Egc2VtYW5hbCBzdSBwZXNvIGVzcGVyYWRvIG8gcHJvbWVkaW8gZGlzbWludXllIGVuIDc3LjU5IGdyYW1vcy4KCi0gICBFbCBjb2VmaWNpZW50ZSAkXGhhdHtcYmV0YV80fSQgZGUgdmFsb3IgLTggZ3JhbW9zLCBjb3JyZXNwb25kZSBhbCBuaXZlbCBkZSBjb25zdW1vIGRpYXJpbyBkZQogICAgYWxjb2hvbCBkZWwgaW5kaXZpZHVvLiBFc3RlIGNvZWZpY2llbnRlIGluZGljYSBxdWUgZGFkYSB1bmEgYWx0dXJhLCB1bmEgZWRhZCB5IGTDrWFzIGRlIGFjdGl2aWRhZAogICAgZsOtc2ljYSBzZW1hbmFsIGZpam9zLCBjYWRhIHZleiBxdWUgZWwgaW5kaXZpZHVvIGNvbnN1bWUgdW4gdHJhZ28gZGUgYWxjb2hvbCBzdSBwZXNvIGVzcGVyYWRvIG8KICAgIHByb21lZGlvIGRpc21pbnV5ZSBlbiA4IGdyYW1vcy4gQSBzaW1wbGUgdmlzdGEgcG9kcsOhIG5vIGxsZWdhciBhIHRlbmVyIHNlbnRpZG8sIHlhIHF1ZSBhIG1heW9yCiAgICBjb25zdW1vIGRlIGFsY29ob2wgZWwgcGVzbyBkZWJlcsOtYSBhdW1lbnRhciwgeWEgc2VhIHBvciBlbCBwZXNvIGRlbCBwcm9waW8gbGlxdWlkbyBjb21vIGVsIHBlc28KICAgIGVxdWl2YWxlbnRlIGVuIGdyYXNhcy4KCioqwr9Tb24gc2lnbmlmaWNhdGl2b3MgbG9zIGNvZWZpY2llbnRlcz8qKgoKUGFyYSBkZXRlcm1pbmEgc2kgbG9zIGNvZWZpY2llbnRlcyBzb24gYXB0b3MgcGFyYSBleHBsaWNhciBlbCBwZXNvIGRlIHVuIGluZGl2aWR1byBzZSByZWFsaXphIHVuCiR7VH0kIHRlc3QgcGFyYSBjYWRhIGNvZWZpY2llbnRlIGVuIGVsIGN1YWwgc2UgZXZhbMO6YW4gbGFzIHNpZ3VpZW50ZXMgaGlww7N0ZXNpczoKCi0gICAke0hfMDogXGJldGFfaSA9IDB9JAotICAgJHtIXzE6IFxiZXRhX2kgXG5lcSAwfSQKClNpICR7XGJldGFfaSBcbmVxIDB9JCBwb2RlbW9zIGRlY2lyIHF1ZSBleGlzdGUgdW5hIGRpZmVyZW5jaWEgZXN0YWRpc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcyBkZWwKY29lZmljaWVudGUgJHtcYmV0YV9pfSQgZGVsIHZhbG9yIGNlcm8sIHkgcG9yIGxvIHRhbnRvIGVsIGNvZWZpY2llbnRlICR7XGJldGFfaX0kIGV4cGxpY2FyIGxhCnZhcmlhYmxlICR7eX0kIChQZXNvIGVuIG51ZXN0cm8gY2FzbykuCgpMdWVnbyBhbmFsaXphbmRvIGxhIHNhbGlkYSBkZSAqKmNvZWZmaWNpZW50c19zdW1tYXJ5KiogY29uY2x1aW1vcyBxdWU6CgotICAgTG9zIGNvZWZpY2llbnRlcyBjb3JyZXNwb25kaWVudGVzIGRlIGxhIGFsdHVyYSgke1xiZXRhXzF9JCkgeSBlZGFkKCR7XGJldGFfMn0kKSB0aWVuZW4gdW4KICAgIHAtdmFsb3IgXDwgMC4wNS4gUG9yIGxvIHRhbnRvIHNlIHJlY2hhemEgbGEgaGlww7N0ZXNpcyBudWxhIHkgYW1ib3MgcmVzdWx0YW4gZXN0YWRpc3RpdGFtZW50ZQogICAgc2lnbmlmaWNhdGl2YXMgcGFyYSBleHBsaWNhciBlbCBwZXNvLiBQb3Igb3RybyBsYWRvLCBzZSBwdWVkZSBhcHJlY2lhciBxdWUgbG9zIGludGVydmFsb3MgZGUKICAgIGNvbmZpYW56YSAoZGVsIDk1JSkgZGUgYW1ib3MgY29lZmljaWVudGVzIG5vIGluY2x1eWVuIGFsIGNlcm8uCi0gICBMbyBjb250cmFyaW8gc3VjZWRlIGNvbiBkw61hcyBkZSBhY3RpdmlkYWQgZsOtc2ljYSBzZW1hbmFsKCR7XGJldGFfM30kKSB5IGNvbnN1bW8gZGUgYWxjb2hvbAogICAgZGlhcmlvICgke1xiZXRhXzR9JCksIGRhdG8gcXVlIGFtYm9zIG5vIHJlY2hhemFyIGxhIGhpcMOzdGVzaXMgbnVsYSAocC12YWxvciA+IDAuMDUpIHkgcG9yIGxvCiAgICB0YW50byBubyBleGlzdGUgdW5hIGRpZmVyZW5jaWEgc2lnbmlmaWNhdGl2YSBkZWwgY2Vyby4gRmluYWxlbW50ZSwgbm8gaGF5IGV2aWRlbmNpYQogICAgZXN0YWRpc3RpdGFtZW50ZSBzaWduaWZpY2F0aXZhcyBwYXJhIGV4cGxpY2FyIGVsIHBlc28uCgoqKsK/RWwgbW9kZWxvIHJlc3VsdGEgc2lnbmlmaWNhdGl2byBwYXJhIGV4cGxpY2FyIGVsIHBlc28/KioKCmBgYHtyfQpnbGFuY2UobW9kZWxfMSkKYGBgCgpQYXJhIGRldGVybWluYXIgc2kgZXMgbW9kZWxvIGVzIHNpZ25pZmljYXRpdm8gcGFyYSBleHBsaWNhciBlbCBwZXNvIGRlIHVuIGluZGl2aWR1byBzZSByZWFsaXphIHVuCiRGJCB0ZXN0IGNvbiBsYXMgc2lndWllbnRlcyBoaXDDs3Rlc2lzOgoKLSAgICRIXzA6IM6yXzEgPSDOsl8yID0gwrcgwrcgwrcgPSDOsl97cOKIkjF9ID0gMCQKLSAgICRIXzE6JCBQb3IgbG8gbWVub3MgdW4gJM6yX2skICgkayA9IDEsIDIsLi4uLCBw4oiSMSQpIGVzIGRpc3RpbnRvIGRlIDAuCgokSF8wJCBhZmlybWEgcXVlIG5vIGhheSB2aW5jdWxvIGVudHJlIGxhIHZhcmlhYmxlICR7eX0kKFBlc28pIHkgbGFzIHZhcmlhYmxlcyByZWdyZXNvcmFzLiAkSF8xJAphZmlybWEgcXVlIGFsIG1lbm9zIHVuYSBkZSBsYXMgdmFyaWFibGVzIHJlZ3Jlc29yYXMgc2lydmUgcGFyYSBwcmVkZWNpciBsYSB2YXJpYWJsZSAke3l9JCAoUGVzbykuCgpWZWFtb3MgbG9zIHJlc3VsdGFkb3MgZGUgJEYkIHRlc3Q6CgpgYGB7cn0KZ2xhbmNlKG1vZGVsXzEpCmBgYAoKRGFkbyBxdWUgZWwgcC12YWxvciBcPCAwLjA1IGUgaWd1YWwgYSAwLCBjb24gbXVjaGEgY2VydGV6YSBwb2RlbW9zIGRlY2lyIHF1ZSBhbCBtZW5vcyB1bmEgZGUgbGFzCnZhcmlhYmxlcyByZWdyZXNvcmFzIHBlcm1pdGUgZXhwbGljYXIgZWwgcGVzby4gRXN0byBjb25jdWVyZGEgY29uIGxvcyByZXN1bHRhZG9zIGRlIGxvcyAkVCQgdGVzdApwYXJhIGxhcyBsb3MgY29lZmljaWVudGVzIGNvcnJlc3BvbmRpZW50ZXMgYSBBbHR1cmEgeSBFZGFkLgoKKirCv1F1w6kgcG9yY2VudGFqZSBkZSBsYSB2YXJpYWJpbGlkYWQgZXhwbGljYSBlbCBtb2RlbG8/KioKClNlZ8O6biBlbCB2YWxvciBkZSAkUl4yJCBhanVzdGFkbyAoKiphZGouci5zcXVhcmVkKiopLCBlc3RlIG1vZGVsbyBsbGVnYSBhIGV4cGxpY2EgZWwgMzUlIGRlIGxhCnZhcmlhYmlsaWRhZCBkZWwgZGF0YXNldCBkZSBlbnRyZW5hbWllbnRvLCBsbyBjdWFsIG5vIGVzIHVuIHZhbG9yIGJham8gcGVybyB0YW1wb2NvIGVzIGRlc3ByZWNpYWJsZS4KCiMjIDMuIE1vZGVsbyBjYXRlZ8OzcmljYXMKClNlIHN1Z2llcmUgcHJvYmFyIHVuIG1vZGVsbyBxdWUgaW5jb3Jwb3JlIGVsIGNvbnN1bW8gc2VtYW5hbCBkZSBzbmFja3MgeSB1bmEgaW50ZXJhY2Npw7NuIGVudHJlIGVsCmfDqW5lcm8geSBsYSBlZGFkLCBlbiBsdWdhciBkZSBhY3RpdmlkYWQgZsOtc2ljYSB5IGNvbnN1bW8gZGUgYWxjb2hvbC4gQWRlbcOhcyBzZSBwaWRlIGV4cGxpY2l0YW1lbnRlCnF1ZSBsYSBjYXRlZ29yw61hICJObyBjb23DrSBjb21pZGEgc2FsYWRhIG8gc25hY2tzIGVuIGxvcyDDumx0aW1vcyA3IGTDrWFzIiBkZSBsYSB2YXJpYWJsZQoqKmNvbnN1bW9fc2VtYW5hbF9zbmFja3MqKiBzZSBlbmN1ZW50cmUgY29tbyBuaXZlbC9jYXRlZ29yw61hIGJhc2FsLgoKJEUocGVzbykgPSBcYmV0YV8wICsgXGJldGFfMSAqIGFsdHVyYSArIFxiZXRhXzIgKiBlZGFkICsgKyBcYmV0YV8zICogZ2VuZXJvICsgXGJldGE0ICogY29uc3Vtb1NlbWFuYWxTbmFja3MgKyBcYmV0YV81ICogZ2VuZXJvICogZWRhZCQKClByaW1lcm8gZGVmaW5pbW9zIGN1YWwgZXMgbGEgcHJpbWVyYSBjYXRlZ29yw61hIGVuIGNhZGEgZmFjdG9yLCB5YSBxdWUgZXN0YSBzZXJhIGxhIHF1ZSBlbCBtb2RlbG8KZGVmaW5hIGNvbW8gY2F0ZWdvcsOtYSBiYXNhbDoKCmBgYHtyfQp0cmFpbl9zZXQkY29uc3Vtb19zZW1hbmFsX3NuYWNrcyA8LSBmYWN0b3IoCiAgdHJhaW5fc2V0JGNvbnN1bW9fc2VtYW5hbF9zbmFja3MsCiAgbGV2ZWxzPWMoIjAiLCI8PTMiLCAiNC02IiwgICI3IiwgIjE0IiwgIjIxIiwgIj49MjgiKSwgCiAgb3JkZXJlZD1GQUxTRQopCnRhYmxlKHRyYWluX3NldCRjb25zdW1vX3NlbWFuYWxfc25hY2tzKQpgYGAKClNlIHB1ZWRlIGFwcmVjaWFyIHF1ZSBsYSBwcmltZXJhIGNhdGVnb3LDrWEgY29ycnNwb25kZW8gYSAwIGNvbnN1bW8gZGUgc25hY2tzIHNlbWFuYWwuCgpgYGB7cn0KdGFibGUodHJhaW5fc2V0JGdlbmVybykKYGBgCgpQb3Igb3RybyBsYWRvIGxhIGNhdGVnb3LDrWEgZ2VuZXJvIGVzdGEgY29ycmVzdGFtZW50ZSBiYWxhbmNlYWRhLgoKKipNb2RlbG8gMioqCgpEZWZpbmltb3MgZWwgbnVldm8gbW9kZWxvOgoKYGBge3J9Cm1vZGVsXzIgPC0gbG0oCiAgcGVzbyB+IGFsdHVyYSArIGVkYWQgKyBnZW5lcm8gKyBjb25zdW1vX3NlbWFuYWxfc25hY2tzICsgIGdlbmVybyAqIGVkYWQsIAogIGRhdGEgPSB0cmFpbl9zZXQKKQpgYGAKCioqwr9DdcOhbCBlcyBsYSBpbnRlcnByZXRhY2nDs24gZGUgbG9zIGNvZWZpY2llbnRlcyBlc3RpbWFkb3MgcGFyYSBsYXMgY2F0ZWdvcsOtYXMgZGUKY29uc3Vtb19zZW1hbmFsX3NuYWNrcyB5IGdlbmVybyBcKiBlZGFkPyDCv1NvbiBzaWduaWZpY2F0aXZhcz8qKgoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9Niwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQpjb2VmZmljaWVudHNfc3VtbWFyeShtb2RlbF8yKQpgYGAKClNpIGludGVycHJldGFtb3MgbG9zIGNvZWZpY2llbnRlcyBxdWUgc29uIHNpZ25pZmljYXRpdm9zOgoKLSAgIExhIGFsdHVyYSB5IGxhIGVkYWQgc2lndWVuIHNpZW5kbyBzaWduaWZpY2F0aXZvcyBhbCBpZ3VhbCBxdWUgZWwgbW9kZWxvIGFudGVyaW9yLCBjb24gbGEKICAgIGRpZmVyZW5jaWEgcXVlIGNhbWJpYXJvbiBsaWdlcmFtZW50ZSBzdXMgdmFsb3JlcyBhIDAuNjQgeSAxLjIxLgoKLSAgIFBhcmEgdW5hICoqYWx0dXJhKiosICoqZWRhZCoqIGZpam9zLCBsYSBkaWZlcmVuY2lhIGRlIHBlc28gZGUgbG9zIGluZGl2aWR1b3MgcXVlIGNvbnN1bWVuIHNuYWNrcwogICAgaGFzdGEgMyB2ZWNlcyBwb3Igc2VtYW5hIGRlIGxvcyBxdWUgbm8gY29uc3VtZW4oQ2F0ZWdvcsOtYSBiYXNhbCkgZXMgZGUgLTEuNDM2IGtnLiBFc3RvIGEgc2ltcGxlCiAgICB2aXN0YSBwYXJlY2llcmEgbm8gdGVuZXIgc2VudGlkbywgeWEgcXVlIHF1aWVuZXMgY29uc3VtZW4gc25hY2tzIGhhc3RhIDMgdmVjZXMgcG9yIHNlbWFuYQogICAgZGViZXLDrWFuIHRlbmVyIHVuIHBlc28gbWVub3IuIExvIG1pc21vIHN1Y2VkZSBjb24gbGFzIGNhdGVnb3LDrWFzICoqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqIDQtNgogICAgeSA+PTI4LgoKKirCv1F1w6kgcG9yY2VudGFqZSBkZSBsYSB2YXJpYWJpbGlkYWQgZXhwbGljYSBlbCBtb2RlbG8/IEVuIGNhc28gZGUgZGV0ZWN0YXIgcXVlIGV4aXN0ZW4gY2F0ZWdvcsOtYXMKbm8gc2lnbmlmaWNhdGl2YXMgZGUgbGEgdmFyaWFibGUgY29uc3Vtb19zZW1hbmFsX3NuYWNrcyBldmFsdWFyIHNpIGxhIHZhcmlhYmxlIGVzIHNpZ25pZmljYXRpdmEgZW4Kc3UgY29uanVudG8geSwgZW4gY2FzbyBhZmlybWF0aXZvLCBwcm9wb25lciB1bmEgcmVkZWZpbmljacOzbiBkZSBsYXMgbWlzbWFzIHF1ZSBwZXJtaXRhIG9idGVuZXIgdW5hCm1heW9yIHByb3BvcmNpw7NuIGRlIGNhdGVnb3LDrWFzIHNpZ25pZmljYXRpdmFzIGluZGl2aWR1YWxtZW50ZS4gTHVlZ28sIGFuYWxpemFyIHNpIGV4aXN0ZW4gY2FtYmlvcyBlbgpsYSB2YXJpYWJpbGlkYWQgZXhwbGljYWRhIHBvciBlbCBtb2RlbG8uKioKClZpZW5kbyBlbCByZXN1bHRhZG8gZGUgKipjb2VmZmljaWVudHNfc3VtbWFyeSoqIHNlIGFwcmVjaWEgcXVlIGxhcyBzaWd1aWVudGVzIGNhdGVnb3LDrWFzIGRlCioqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqIG5vIHNvbiBzaWduaWZpY2F0aXZhczoKCi0gICAyIHZlY2VzIGFsIGTDrWEgKDE0IHZlY2VzL3NlbWFuYSkKLSAgIDQgYSA2IHZlY2VzIGR1cmFudGUgbG9zIMO6bHRpbW9zIDcgKDQgYSA2IHZlY2VzIHNlbWFuYSkKLSAgIDMgdmVjZXMgYWwgZMOtYSAoMjEgdmVjZXMvc2VtYW5hKQoKUGVybyBzaSBzb24gc2lnbmlmaWNhdGl2YXMgbG9zIGV4dHJlbW9zOgoKLSAgIERlIDEgYSAzIHZlY2VzL3NlbWFuYQoKLSAgIERlIDI4IG8gbWFzIHZlY2VzL3NlbWFuYQoKQSBjb250aW51YWNpw7NuIHNlIHJlYWxpemEgdW4gJEYkIHRlc3QgcGFyYSBldmFsdWFyIGxhIHNpZ25pZmljYXRpdmlkYWQgY29uanVudGEgZGUgbGFzIGNhdGVnw7NyaWNhcwpkZSBsYSB2YXJpYWJsZSAqKmNvbnN1bW9fc2VtYW5hbF9zbmFja3MqKiBwYXJhIGV4cGxpY2FyIGVsIHBlc28uCgpFbCAkRiQgdGVzdCB0YW1iacOpbiBsbGFtYW5kbyBBTk9WQSAoQW7DoWxpc2lzIGRlIGxhIHZhcmlhcsOtYW4pIHNlIHJlYWxpemEgcGFyYSBwcm9iYXIgbGEKc2lnbmlmaWNhdGl2aWRhZCBjb25qdW50YSBkZSB0b2RvcyBsb3MgdmFsb3JlcyBkZSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EuCgpMYXMgaGlww7N0ZXNpcyBzb24gbGFzIHNpZ3VpZW50ZXM6CgotICAgJEhfMDogzrJfcSA9IM6yX3txKzF9ID0gwrcgwrcgwrcgPSDOsl97cOKIkjF9ID0gMCQKCi0gICAkSF8xOiQgcG9yIGxvIG1lbm9zIHVubyBkZSBsb3MgJM6yX2skIChjb24gJGskIGVudHJlICRxJCB5ICRw4oiSMSQpIGVzIHRhbCBxdWUgJM6yX2sgXG5lcSAwJC4KCkx1ZWdvIHNpIHRvZG9zbCBsb3MgY29lZmljaWVudGVzIGFzb2NpYWRvcyBhIGxvcyB2YWxvcmVzIGRlIHZhcmlhYmxlIGNhdGVnw7NyaWNhIHNvbiBjZXJvLCBzZSByZWNoYXphCmxhIGhpcMOzdGVzaXMgbnVsYSB5IHBvciBsbyB0YW50byBsYSB2YXJpYWJsZSBubyBlcyBzaWduaWZpY2FydGl2YSBwYXJhIGV4cGxpY2FyIGVsIHBlc28gZW4gbnVlc3RybwpjYXNvLgoKQSBjb250aW51YWNpw7NuIHZlcmVtb3MgZWwgcC12YWxvciByZXN1bHRhZG8gZGUgYXBsaWNhciAkRiQgdGVzdCBwYXJhIGNhZGEgdmFyaWFibGUgZGVsIG1vZGVsbzoKCmBgYHtyfQphbm92YV9zdW1tYXJ5KG1vZGVsXzIpCmBgYAoKUG9kZW1vcyBhcHJlY2lhciBxdWUgZWwgcC12YWx1ZSBcPCAwLjAwNSBwYXJhIGxhIHZhcmlhYmxlICoqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqLiBQb3IgbG8gdGFudG8Kc2UgcmVjaGF6YSBsYSBoaXDDs3Rlc2lzIG51bGEgeSBwb2RlbW9zIGRlY2lyIGVuIHN1IGNvbmp1bnRvIHJlc3VsdGEgZXN0YWTDrXN0aWNhbWVudGUgc2lnbmlmaWNhdGl2YQpwYXJhIGV4cGxpY2FyIGVsIHBlc28uIEx1ZWdvLCBjb21vIGxhIHZhcmlhYmxlICoqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqIGVzIHNpZ25pZmljYXRpdmEgdmFsZSBsYQpwZW5hIHJlLWRlZmluaXJsYS4KClBvciBvdHJvIGxhZG8sIGxhIGNvbWJpbmFjacOzbiBkZSB2YXJpYWJsZXMgZ2VuZXJvLWVkYWQgbm8gZXMgZXN0YWTDrXN0aWNhbWVudGUgc2lnbmlmaWNhdGl2YSBwYXJhCmV4cGxpY2FyIGVsIHBlc28sIHBlcm8gc2kgbG8gZXMgZWwgZ2VuZXJvIGVuIGZvcm1hIHNlcGFyYWRhLiBGaW5hbG1lbnRlLCBjb21vIHlhIHZpbW9zIGVuIHBhc2lzCmFudGVyaW9yZXMsIGVkYWQgeSBhbHR1cmEgc29uIHNpZ25pZmljYXRpdmFzLgoKVmVhbW9zIGEgY29udGludWFjacOzbiBsYXMgZGlzdHJpYnVjaW9uZXMgZGUgbGFzIGNhdGVnb3LDrWFzIGRlIGxhIHZhcmlhYmxlICoqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqCm9yZGVuYWRhcyBwb3IgbGEgbWVkaWFuYSBkZWwgcGVzbzoKCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTQsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30Kc2VnbWVudGVkX2JveF9wbG90KAogIHRyYWluX3NldCwgCiAgY29sdW1uICAgICAgICA9ICdwZXNvJywgCiAgc2VnbWVudGVkX2J5ICA9ICdjb25zdW1vX3NlbWFuYWxfc25hY2tzJywKICB0aXRsZSAgICAgICAgID0gJ0NvbnN1bW8gZGUgc25hY2tzIG9yZGVuYWRvIHBvciBsYSBtZWRpYW5hIGRlbCBwZXNvJywKICB5X2xhYmVsICAgICAgID0gJ1Blc28gKEtnKScsCiAgeV9saW1pdHMgICAgICA9IGMoMTAsIDEzMCksCiAgeF9sYWJlbCAgICAgICA9ICdDb25zdW1vIGRlIHNuYWNrcyAoVmVjZXMvU2VtYW5hKScKKQpgYGAKCkEgc2ltcGxlIHZpc3RhIG5vIHBhcmVjZSBoYWJlciB1bmEgZ3JhbiBkaWZlcmVuY2lhLCBwZXJvIHNpIHNlIGFwcmVjaWEgcXVlIGxvcyBleHRyZW1vcyBkaWZpZXJlbiBkZWwKbG9zIHZhbG9yZXMgY2VudHJhbGVzLgoKQSBjb250aW51YSBjb24gc2UgcHJvbWFuZSB1bmEgbnVldmEgZGVmaW5pY2nDs24gZGUgbGEgdmFyaWFibGUgKipjb25zdW1vX3NlbWFuYWxfc25hY2tzKiouIFByaW1lcm8gc2UKcmVhbGl6YSBlbCBwcm9tZWRpb2RlbCBwZXNvIHBhcmEgY2FkYSBjYXRlZ29yw61hIGRlIGxhIHZhcmlhYmxlICoqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqOgoKYGBge3IgZmlnLmhlaWdodD0yLCBmaWcud2lkdGg9NCwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQpwZXNvX21lZGlvX2J5X25pdmVsX2NvbnN1bW9fc25hY2sgPSB0cmFpbl9zZXQgJT4lIAogIGdyb3VwX2J5KGNvbnN1bW9fc2VtYW5hbF9zbmFja3MpICU+JQogIHN1bW1hcmlzZShwcm9tZWRpbyA9IG1lYW4ocGVzbykpCgpnZ3Bsb3QoZGF0YSA9IHBlc29fbWVkaW9fYnlfbml2ZWxfY29uc3Vtb19zbmFjaywgYWVzKHggPSBwcm9tZWRpbykpICsgCiAgZ2VvbV9ib3hwbG90KGFscGhhID0gMC43NSwgZmlsbD0iYmx1ZSIpICsKICBsYWJzKHRpdGxlID0gIlBlc28gcHJvbWVkaW8gcG9yIGNhZGEgY2F0ZWdvcmlhIGRlIGNvbnN1bW8gZGUgc25hY2tzIikgKwogIGxhYnMoeCA9ICJQZXNvIG1lZGlvIikgKwogIHRoZW1lX2J3KCkKYGBgCgpgYGB7cn0KcGVzb19tZWRpb19ieV9uaXZlbF9jb25zdW1vX3NuYWNrICA8LSBwZXNvX21lZGlvX2J5X25pdmVsX2NvbnN1bW9fc25hY2skcHJvbWVkaW8KCnBlc29fbWVkaW9fYnlfbml2ZWxfY29uc3Vtb19zbmFjawpxdWFudGlsZShwZXNvX21lZGlvX2J5X25pdmVsX2NvbnN1bW9fc25hY2spCmBgYAoKU2UgcHVlZGUgYXByZWNpYXIgcXVlIGVzIHVuYSBkaXN0cmlidWNpw7NuIGFzaW3DqXRyaWNhIHNlc2dhZGEgYSBkZXJlY2hhLCB5YSBxdWUgbG9zIG1heW9yZXMgdmFsb3JlcwpzZSBlbmN1ZXJhbiBhcnJpYmEgZGVsIHNlZ3VuZG8gY3VhbnRpbCAoTWVkaWFuYSkuCgpBIGNvbnRpbnVhY2nDs24gc2UgcmUtZGVmaW5lbiBsYXMgY2F0ZWdvcsOtYXMgb3JpZ2luYWxlcyBwb3IgMyBudWV2YSBjYXRlZ29yw61hczogQmFqbywgTWVkaW8sIEFsdG8uCkVzdGEgY2F0ZWdvcsOtYXMgZXN0YW4gYXNvY2lhZGFzIGFsIHBlc28gZGUgZGUgaW5kaXZpZHVvLiBTaSBlbCBpbmRpdsOtZGlvIHRpZW5lIHVuIHBlc28gbWVub3IgYWwgUTEsCnNlIGxlIGFzaWduYSBlbCBuaXZlbCBCYWpvLCBzaSBlc3RhIGVudHJlIFExIHkgUTMgc2VyYSBNZWRpbyB5IEFsdGEgYXJyb2JhIGRlIFEzLiBGaW5hbG1lbnRlIGEKY29udGludWFjacOzbiBzZSB0cmFuc2Zvcm1hIGxhIHZhcmlhYmxlIGVuIGVsIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG8geSBlbiB0ZXN0IHNlIHVzYXMgbG9zIGxvcwpjdWFudMOtbGVzIGdlbmVyYWRvcyBjb24gZW4gY29uanVudG8gZGUgZW50cmVuYW1pZW50bzoKCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTMsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KcTEgPC0gcXVhbnRpbGUocGVzb19tZWRpb19ieV9uaXZlbF9jb25zdW1vX3NuYWNrKVsyXQpxMyA8LSBxdWFudGlsZShwZXNvX21lZGlvX2J5X25pdmVsX2NvbnN1bW9fc25hY2spWzRdCgp0cmFpbl9zZXQyIDwtIHRyYWluX3NldCAlPiUKICBtdXRhdGUoY29uc3Vtb19zZW1hbmFsX3NuYWNrcyA9IGNhc2Vfd2hlbihwZXNvIDwgIHExIH4gIkJham8iLCBwZXNvID49IHEzIH4gIkFsdG8iLCBUUlVFIH4gIk1lZGlvIikpCgp0ZXN0X3NldDIgPC0gdGVzdF9zZXQgJT4lCiAgbXV0YXRlKGNvbnN1bW9fc2VtYW5hbF9zbmFja3MgPSBjYXNlX3doZW4ocGVzbyA8ICBxMSB+ICJCYWpvIiwgcGVzbyA+PSBxMyB+ICJBbHRvIiwgVFJVRSB+ICJNZWRpbyIpKSAlPiUgCiAgbXV0YXRlKGNvbnN1bW9fc2VtYW5hbF9zbmFja3MgPSBhcy5mYWN0b3IoY29uc3Vtb19zZW1hbmFsX3NuYWNrcykpCgp0ZXN0X3NldDIgJT4lIHNlZ21lbnRlZF9ib3hfcGxvdCgKICBjb2x1bW4gICAgICAgID0gJ3Blc28nLCAKICBzZWdtZW50ZWRfYnkgID0gJ2NvbnN1bW9fc2VtYW5hbF9zbmFja3MnLAogIHRpdGxlICAgICAgICAgPSAnQ29uc3VtbyBkZSBzbmFja3Mgb3JkZW5hZG8gcG9yIGxhIG1lZGlhbmEgZGVsIHBlc28gZW4gVGVzdCcsCiAgeV9sYWJlbCAgICAgICA9ICdQZXNvIChLZyknLAogIHlfbGltaXRzICAgICAgPSBjKDQwLCAxMDApLAogIHhfbGFiZWwgICAgICAgPSAnQ29uc3VtbyBkZSBzbmFja3MgKFZlY2VzL1NlbWFuYSknCikKYGBgCgpTZSBwdWVkZSBhcHJlY2lhciBxdWUgZW4gZWwgY29uanVudG8gZGUgdGVzdCBubyBoYXkgdmFsb3JlcyBtZWRpbywgcGVybyBleGlzdGUgZW4gZWwgY29uanVudG8gZGUKZW50cmVuYW1pZW50by4KCioqTW9kZWxvIDMqKgoKQSBjb250aW51YWNpw7NuIGRlZmluaW1vcyB1biBudWV2byBtb2RlbG8gaWd1YWwgYWwgYW50ZXJpb3IgcGVybyBhaG9yYSB5YSB1c2FuZG8gbGEgcmUtZGVmaW5pY2nDs24gZGUKbGEgdmFyaWFibGUgKipjb25zdW1vX3NlbWFuYWxfc25hY2tzOioqCgpgYGB7cn0KbW9kZWxfMyA8LSBsbSgKICBwZXNvIH4gYWx0dXJhICsgZWRhZCArIGdlbmVybyArIGNvbnN1bW9fc2VtYW5hbF9zbmFja3MgKyAgZ2VuZXJvICogZWRhZCwgCiAgZGF0YSA9IHRyYWluX3NldDIKKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTYsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30KbW9kZWxzIDwtIGxpc3QoJ01vZGVsbyAxJz1tb2RlbF8xLCAnTW9kZWxvIDInPW1vZGVsXzIsICdNb2RlbG8gMyc9bW9kZWxfMykKCmNvZWZmaWNpZW50c19zdW1tYXJ5KG1vZGVsXzMpCmFub3ZhX3N1bW1hcnkobW9kZWxfMykKCm1vZGVscyAlPiUgCiAgbWFwX2RmKGdsYW5jZSwgLmlkID0gIm1vZGVsIikgJT4lCiAgYXJyYW5nZShkZXNjKGFkai5yLnNxdWFyZWQpKQpgYGAKCkZpbmFsbWVudGUgc2UgYXByZWNpYSBxdWUgbGEgbnVldmEgY2F0ZWdvcml6YWNpw7NuIGRlIGxhIHZhcmlhYmxlIGF1bWVudGEgZWwgJFJeMiQgQWp1c3RhZG8gYSBjYXNpIGVsCmRvYmxlIChEZWwgMzUuNyBhbCA2NC43JSkuIFNpIGJpZW4gZXN0byBtZWpvcmEgbGEgY2FwYWNpZGFkIGV4cGxpY2F0aXZhIGRlbCBtb2RlbG8sIGVuIHBhc29zCnBvc3RlcmlvcmVzIHNlIGRlYmVyw6EgZGV0ZXJtaW5hIHNpIHByb2R1Y2UgbyBubyBvdmVyZml0dGluZyBzb2JyZSBlbCBjb25qdW50byBkZSBlbnRyZW5hbWllbnRvLgoKIyMgNC4gTW9kZWxvcyBwcm9waW9zIHkgZXZhbHVhY2nDs24KClJlYWxpemFyIDIgbW9kZWxvcyBsaW5lYWxlcyBtw7psdGlwbGVzIGFkaWNpb25hbGVzIHkgZXhwbGljYXIgYnJldmUtbWVudGUgbGEgbMOzZ2ljYSBkZXRyw6FzIGRlIGxvcwptaXNtb3MgKHNlIHZhbG9yYXLDoSBsYSBjcmVhY2nDs24geS9vIGluY2x1c2nDs24gZGUgdmFyaWFibGVzIG51ZXZhcykuCgpFdmFsdWFyIGxhIHBlcmZvcm1hbmNlIGRlbCBtb2RlbG8gaW5pY2lhbCwgZWwgbW9kZWxvIGNhdGVnw7NyaWNhcyBjb24gbGFzIGNhdGVnb3LDrWFzIHJlZGVmaW5pZGFzIGRlCmxhIHZhcmlhYmxlICoqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqIHkgbG9zIG1vZGVsb3MgZGVzYXJyb2xsYWRvcyBlbiBlc3RlIHB1bnRvIGVuIGVsIGRhdGFzZXQgZGUKZW50cmVuYW1pZW50byB5IGV2YWx1YWNpw7NuICh1c2FyIGRhdGFzZXQgImVuY3Vlc3RhX3NhbHVkX3Rlc3QuY3N2IikuCgpMYSBldmFsdWFjacOzbiBkZSBwZXJmb3JtYW5jZSBjb25zaXN0ZSBlbiBjb21wYXJhciBlbiBhbWJvcyBzZXRzIGxhIHBlcmZvcm1hbmNlIGVuIHTDqXJtaW5vcyBkZWwgUgpjdWFkcmFkbyBhanVzdGFkbywgUk1TRSB5IE1BRS4KCkFsIGNvbnRpbnVhY2nDs24gc2UgZGVmaW5lIDIgbW9kZWxvcy4KCioqTW9kZWxvIDQqKgoKJEUocGVzbykgPSBcYmV0YV8wICsgXGJldGFfMSAqIGFsdHVyYSArIFxiZXRhXzIgKiBlZGFkICsgKyBcYmV0YV8zICogZ2VuZXJvICsgXGJldGE0ICogY29uc3Vtb1NlbWFuYWxTbmFja3MgKyBcYmV0YV81ICogZGlhc0FjdGl2aWRhZEZpc2ljYVNlbWFuYWwgKyBcYmV0YV82ICogYWx0dXJhICogZ2VuZXJvJAoKU2UgdXRpbGl6byBsYSByZWRlZmluaWNpw7NuIGRlIGxhIHZhcmlhYmxlICoqY29uc3Vtb19zZW1hbmFsX3NuYWNrcyoqIGNvbW8gYmFzZS4gQWRlbWFzZSBzZSBhZ3JlZ2FyCmxhIHZhcmlhYmxlIGRpYXNfYWN0aXZpZGFkX2Zpc2ljYV9zZW1hbmFsIGVudGVuZGllbmRvIHF1ZSB0aWVuZSB1bmEgaW5mbHVlbmNpYSBpcG9ydGFudGUgZW4gZWwgcGVzbwp5IGx1ZWdvIGxhIGFzb2NpYWNpb24gYWx0dXJhIFwqIGdlbmVybyB5YSBxdWUgZW4gZ2VuZXJhbCBtYXMgbXVqZXJlcyB0aWVuZW4gYSBzZXIgbWFzIGJhamFyIHF1ZSBsb3MKdmFyb25lcyB5IHZpc2UgdmVyc2EuCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD02LCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9Cm1vZGVsXzQgPC0gbG0oCiAgcGVzb34gCiAgICBhbHR1cmEgKyAKICAgIGVkYWQgKyAKICAgIGdlbmVybyArIAogICAgY29uc3Vtb19zZW1hbmFsX3NuYWNrcyArCiAgICBkaWFzX2FjdGl2aWRhZF9maXNpY2Ffc2VtYW5hbCArCiAgICBhbHR1cmEqZ2VuZXJvLAogIGRhdGEgPSB0cmFpbl9zZXQyCikKCmNvZWZmaWNpZW50c19zdW1tYXJ5KG1vZGVsXzQpCmFub3ZhX3N1bW1hcnkobW9kZWxfNCkKZ2xhbmNlKG1vZGVsXzQpCmBgYAoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9NSwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQp0cmFpbl9zZXQzIDwtIGNvbHVtbl9tZWFuX3F1YW50aWxlX2Jpbm5pbmcodHJhaW5fc2V0MiwgJ2RpYXNfYWN0aXZpZGFkX2Zpc2ljYV9zZW1hbmFsJykKdGVzdF9zZXQzICA8LSBjb2x1bW5fbWVhbl9xdWFudGlsZV9iaW5uaW5nKHRlc3Rfc2V0MiwgICdkaWFzX2FjdGl2aWRhZF9maXNpY2Ffc2VtYW5hbCcpCgp0cmFpbl9zZXQzIDwtIGNvbHVtbl9tZWFuX3F1YW50aWxlX2Jpbm5pbmcodHJhaW5fc2V0MywgJ2NvbnN1bW9fc2VtYW5hbF9mcnV0YXMnKQp0ZXN0X3NldDMgIDwtIGNvbHVtbl9tZWFuX3F1YW50aWxlX2Jpbm5pbmcodGVzdF9zZXQzLCAgJ2NvbnN1bW9fc2VtYW5hbF9mcnV0YXMnKQoKdHJhaW5fc2V0MyA8LSBjb2x1bW5fbWVhbl9xdWFudGlsZV9iaW5uaW5nKHRyYWluX3NldDMsICdjb25zdW1vX3NlbWFuYWxfdmVyZHVyYScpCnRlc3Rfc2V0MyAgPC0gY29sdW1uX21lYW5fcXVhbnRpbGVfYmlubmluZyh0ZXN0X3NldDMsICAnY29uc3Vtb19zZW1hbmFsX3ZlcmR1cmEnKQoKdHJhaW5fc2V0MyA8LSBjb2x1bW5fbWVhbl9xdWFudGlsZV9iaW5uaW5nKHRyYWluX3NldDMsICdjb25zdW1vX3NlbWFuYWxfY29taWRhX2dyYXNhJykKdGVzdF9zZXQzICA8LSBjb2x1bW5fbWVhbl9xdWFudGlsZV9iaW5uaW5nKHRlc3Rfc2V0MywgICdjb25zdW1vX3NlbWFuYWxfY29taWRhX2dyYXNhJykKCnRyYWluX3NldDMgPC0gY29sdW1uX21lYW5fcXVhbnRpbGVfYmlubmluZyh0cmFpbl9zZXQzLCAnY29uc3Vtb19zZW1hbmFsX2dhc2Vvc2FzJykKdGVzdF9zZXQzICA8LSBjb2x1bW5fbWVhbl9xdWFudGlsZV9iaW5uaW5nKHRlc3Rfc2V0MywgICdjb25zdW1vX3NlbWFuYWxfZ2FzZW9zYXMnKQoKc2VnbWVudGVkX2JveF9wbG90KAogIHRlc3Rfc2V0MywgCiAgY29sdW1uICAgICAgICA9ICdwZXNvJywgCiAgc2VnbWVudGVkX2J5ICA9ICdkaWFzX2FjdGl2aWRhZF9maXNpY2Ffc2VtYW5hbCcsCiAgdGl0bGUgICAgICAgICA9ICdOaXZlbGVzIGFjdGl2aWRhZCBmaXNpY2Egb3JkZW5hZG9zIHBvciBsYSBtZWRpYW5hIGRlbCBwZXNvIGVuIFRlc3QnLAogIHlfbGFiZWwgICAgICAgPSAnUGVzbyAoS2cpJywKICB5X2xpbWl0cyAgICAgID0gYyg0MCwgMTAwKSwKICB4X2xhYmVsICAgICAgID0gJ05pdmVsZXMgZGUgYWN0aXZpZGFkIGbDrXNpY2EgKERpYXMpJwopCmBgYAoKKipNb2RlbG8gNSoqCgokRShwZXNvKSA9IFxiZXRhXzAgKyBcYmV0YV8xICogYWx0dXJhICsgXGJldGFfMiAqIGVkYWQgKyArIFxiZXRhXzMgKiBnZW5lcm8gKyBcYmV0YTQgKiBjb25zdW1vU2VtYW5hbFNuYWNrcyArIFxiZXRhXzUgKiBkaWFzQWN0aXZpZGFkRmlzaWNhU2VtYW5hbCArIFxiZXRhXzYgKiBjb25zdW1vU2VtYW5hbEZydXRhcyArIFxiZXRhXzcgKiBjb25zdW1vU2VtYW5hbFZlcmR1cmFzICsgXGJldGFfOCAqIGNvbnN1bW9TZW1hbmFsR3Jhc2FzICsgXGJldGFfOSAqIGNvbnN1bW9TZW1hbmFsR2FzZW9zYXMkCgpTZSB1dGlsaXpvIGxhIHJlZGVmaW5pY2nDs24gZGUgbGEgdmFyaWFibGUgKipjb25zdW1vX3NlbWFuYWxfc25hY2tzKiogY29tbyBiYXNlLiBBZGVtYXNlIHNlIGFncmVnYXIKbGEgdmFyaWFibGUgY29uc3Vtb19zZW1lbmFsX2ZydXRyYXMvdmVyZHVyYXMvZ3Jhc2FzL2dhc2Vhb3NhcyBlbnRlbmRpZW5kbyBxdWUgdGFtYmnDqW4gdGllbmUgdW5hCmluZmx1ZW5jaWEgaW1wb3J0YW50ZSBlbiBlbCBwZXNvLgoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9Niwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQptb2RlbF81IDwtIGxtKAogIHBlc28gfiAKICBlZGFkICsKICBnZW5lcm8gKwogIGFsdHVyYSArCiAgY29uc3Vtb19zZW1hbmFsX3NuYWNrcyArCiAgY29uc3Vtb19zZW1hbmFsX2ZydXRhcyArIAogIGNvbnN1bW9fc2VtYW5hbF92ZXJkdXJhLAogIGRhdGEgPSB0cmFpbl9zZXQzCikKCmNvZWZmaWNpZW50c19zdW1tYXJ5KG1vZGVsXzUpCmFub3ZhX3N1bW1hcnkobW9kZWxfNSkKZ2xhbmNlKG1vZGVsXzUpCmBgYAoKYGBge3J9CmMobW9kZWxzLCBsaXN0KCdNb2RlbG8gNCc9bW9kZWxfNCwgJ01vZGVsbyA1Jz1tb2RlbF81KSkgJT4lIAogIG1hcF9kZihnbGFuY2UsIC5pZCA9ICJtb2RlbCIpICU+JQogIGFycmFuZ2UoZGVzYyhhZGouci5zcXVhcmVkKSkKYGBgCgpGaW5hbG1lbnRlLCBzaSBjb21wYXJhbW9zIGxvcyBtb2RlbG9zIHBvciAkUl4yJCBBanVzdGFkbywgc2UgcHVlZGUgYXByZWNpYXIgcXVlIGVsIG1vZGVsbyA1IChjb24KdG9kYXMgbGFzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgcmUtZGVmaW5pZGFzKSBsbGVnYSBhIGNhcHRhciBsYSBtYXlvciB2YXJpYW56YSBleHBsaWNhZGEgc29icmUgZWwKZGF0YXNldCBkZSBlbnRyZW5hbWllbnRvLiBQb3Igc3VwdWVzdG8gZXN0byBubyBkaWNlIG5hZGEgYWNlcmNhIGRlIGxhIHBlcmZvcm1hbmNlIGRlbCBtb2RlbG8gZW4KdGVzdCwgcGVybyBzaSBxdWUgdGllbmUgbGEgbWVqb3IgY2FwYWNpZGFkIHBhcmEgZXh0cmFlciBpbmZvcm1hY2nDs24gZGUgbG9zIGRhdG8gZGUgZW50cmVuYW1pZW50by4KCioqwr9DdcOhbCBlcyBlbCBtZWpvciBtb2RlbG8gcGFyYSBudWVzdHJvIG9iamV0aXZvIGRlIHByZWRlY2lyIGVsIHBlc28/IMK/UG9yIHF1w6k/KioKCkFob3JhIGNvbXBhcmFtb3MgbGEgcGVyZm9ybWFuY2UgZGUgdG9kbyBsb3MgbW9kZWxvcyBhbCBldmFsdWFyIGVsIGVycm9yIGRlbG9zIG1pc21vIGFsIHByZWRlY2lyIGVsCnBlc28gZW4gZWwgY29uanVudG8gZGUgdHJhaW4geSB0ZXN0IHRhbnRvIHBhcmEgUk1TRSBjb21vIE1BRToKCioqUk1TRSoqCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIHdhcm5pbmc9RkFMU0V9CmN1c3RvbV9tb2RlbHNfZXZhbHVhdGlvbl9zdW1tYXJ5KAogIG1vZGVsXzEsIG1vZGVsXzIsIG1vZGVsXzMsIG1vZGVsXzQsIG1vZGVsXzUsCiAgdGVzdF9zZXQsIHRlc3Rfc2V0MiwgdGVzdF9zZXQzLAogIG1ldHJpY19mbiA9IHJtc2UKKQpgYGAKClNpIHV0aWxpemFtb3MgbGEgbcOpdHJpY2EgUk1TRSBwb2RlbW9zIHZlciBxdWUgZWwgbW9kZWxvIDUgdGllbmUgZWwgbWVub3IgZXJyb3IgZW4gZWwgY29uanVudG8gZGUKdGVzdC4gUG9yIG90cm8gbGFkb3MgZWwgcXVlIHRpZW5lIGxhIG1heW9yIGRpZmVyZW5jaWEgZGUgZXJyb3IgZW50cmUgdGVzdCB5IGVudHJlbmFtaWVudG8uIEVzdG8gbm9zCmRpY2UgcXVlIHBvZHLDrWEgZXN0YXIgc29icmUtYWp1c3RhbmRvc2UgYWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50by4gRWwgbW9kZWxvIDMgdGllbmUgdW4gZXJyb3IgZW4KdGVzdCBtdXkgY2VyY2FubyB5IGFkZW1hcyB0aWVuZSB1biBkaWZlcmVuY2lhIGVudHJlIHRlc3QgeSB0cmFpbiBtdWNobyBtZW5vci4gcG9yIGVzdG8gdWx0aW1vIHBhcmVjZQpzZXIgZWwgbWVqb3IgbW9kZWxvIHlhIHF1ZSB0aWVuZSBwcsOhY3RpY2FtZW50ZSBlbCBtZW5vciBlcnJvciBwb3NpYmxlIHkgdGFtYmnDqW4gZWwgbWVub3IKc29icmUtYWp1c3RlIGFsIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG8uCgoqKk1BRSoqCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpjdXN0b21fbW9kZWxzX2V2YWx1YXRpb25fc3VtbWFyeSgKICBtb2RlbF8xLCBtb2RlbF8yLCBtb2RlbF8zLCBtb2RlbF80LCBtb2RlbF81LAogIHRlc3Rfc2V0LCB0ZXN0X3NldDIsIHRlc3Rfc2V0MywKICBtZXRyaWNfZm4gPSBtYWUKKQpgYGAKClNpIG1lZGltb3MgYSBwYXJ0aXIgZGVsIE1BRSBzdWNlZGUgYWxnbyBtdXkgc2ltaWxhciwgRWwgbW9kZWxvIDMgZXMgZXMgcXVlIHRpZW5lIG1lbm9yIGVycm9yIHkKYWRlbWFzIG1lbm9zIHNvYnJlLWFqdXN0ZS4KCkZpbmFsbWVudGUsIHNlZ8O6biBhbWJhcyBtZXRyaWNhcyBlbCBtb2Vqb3IgbW9kZWxvIGVzIGVsICoqTW9kZWxvIDMqKi4KCiMjIDUuIERpYWduw7NzdGljbyBkZWwgbW9kZWxvCgpBbmFsaXphciBlbiBwcm9mdW5kaWRhZCBlbCBjdW1wbGltaWVudG8gZGUgbG9zIHN1cHVlc3RvcyBkZWwgbW9kZWxvIGxpbmVhbCBwYXJhIGVsIG1vZGVsbyBpbmljaWFsLgoKYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9Niwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQpwbG90KG1vZGVsXzEpCmBgYAoKKipIb21vc2VkYXN0aXNpZGFkKioKCkFsIHZpc3VhbGl6YXIgZWwgcHJpbWVyIGdyw6FmaWNvIChSZXNpZHVvcyB2cy4gVmFsb3JlcyBhanVzdGFkb3MpIHNlIHB1ZWRlIGFwcmVjaWFyIHF1ZSBubyBoYXkKcHJlc2VuY2lhIGRlIGhvbW9jZWRhc3RyaXNpc2RhZCwgeWEgcXVlIGxvcyB2YWxvcmVzIHByZWRpY2hvLCBsYSB2YXJpYWJpbGlkYWQgbyBhbXBsaXR1ZCBkZSBsb3MKcmVzaWR1b3MgcGFyZWNlIG1hbnRlbmVyc2UgY29uIGNpZXJ0YSByZWd1bGFyaWRhZC4gRGFkYXMgZXN0YSBjb25kaWNpb25lcyBwb2RlbW9zIGRlY2lyIHF1ZSBzZQpjdW1wbGUgZWwgc3VwdWVzdG8gZGUgdmFyaWFuemEgY29uc3RhbnRlLgoKKipOb3JtYWxpZGFkKioKCkFsIHZpc3VhbGl6YXIgZWwgZGlhZ3JhbWEgKipRUS1QbG90KiogcG9kZW1vcyBvYnNlcnZhcyBxdWUgZW4gZWwgZXh0cmVtbyBkZXJlY2hhLCBlbCBtb2RlbG8gc29icmUKZXN0aW1hIGVsIHBlc28gZGVsIGxvcyBpbmRpdmlkdW9zIHlhIHF1ZSBoYXkgdW5hIGdyYW4gZGlmZXJlbmNpYSBwb3NpdGl2YSBlbnRyZSBlbCB2YWxvciBwcmVkaWNobyB5CmVsIHZhbG9yIGVzcGVyYWRvIHRlw7NyaWNvLiBsbyBtaXMgc3VjZWRlIGEgaXpxdWllcmNhIHBlcm8gZW4gbWVub3IgbWVkaWRhLCBkb25kZSBlbCBtb2RlbG8gc3ViZXN0aW1hCmVsIHZhbG9yIGRlIHBlc28gZW4gY29tcGFyYWNpw7NuIGFsIHZhbG9yIGVzcGVyYWRvIHRlw7NyaWNvLiBDb21vIGRhdG8gYWRpY2lvbmEgZXN0ZSBncmFmaXRvCmNvcnJlc3BvbmRlIGEgdW5hIGRpc3RyaWJ1Y2nDs24gc2VzZ2FkYSBhIGRlcmVjaGEsIHRhbWJpw6luIGNvbm9jaWRvIGNvbW8gc2VzZ28gcG9zaXRpdm8uIEZpbmFsbWVudGUKZWwgUVEtUGxvdCBubyBtdWVzdHJhIHVuIGdyYWRvIGRlIGFsZWphbWllbnRvIHByb251bmNpYWRvIGRlIHVuYSBkaXN0cmljaW9uIG5vcm1hbCB0ZcOzcmljYSB5IGRlY2ltb3MKcXVlIG5vIHNlIGN1bXBsZSBlbCBzdXB1ZXN0byBkZSBub3JtYWxpZGFkIGRlbCBtb2RlbG8uCgoqKkFwYWxhbmNhbWllbnRvIChMZXZlcmFnZSkqKgoKU2kgb2JzZXJ2YW1vcyBlbCBncsOhZmljbyBkZSAqKlJlc2lkdW9zIHZzIEFwYWxhY2FtaWVudG8qKiB2ZW1vcyBxdWUgdmFyaWFzIG9ic2VydmFjaW9uZXMgbwppbmRpdmlkdW9zIHF1ZSBzZSBhbGVqYW4gZGVsIGN1bXVsbyBkZSBwcmluY2lwYWwuIEVzdG9zIGVqZXJjZW4gdW4gYXB1bnRhbGFtaWVudG8gc29icmUgZWwgdmFsb3JlcwpwcmVkaWNobyBkZWwgbW9kZWxvIGEgcGFydGlyIGRlIHVuIGFwYWxhbmNhbWllbnRvKGxldmVyYWdlKSAwLjAwMjAgeSBlcyBtYXMgcHJvbnVuY2lhZG8gZGVzZGUKMC4wMDI1LiBGaW5hbG1lbnRlIHZlbW9zIHVuIGdyYWRvIGltcG9ydGFudGUgZGUgZGVzdmnDsyBkZSBsYXMgcHJlZGljY2lvbmVzIHZzIHN1IHZvciBlc3BlcmFkby4KCkEgY29udGludWFjacOzbiBzZSBwdWVkZW4gdmVyIGxvIGluZGl2aWR1b3MgcXVlIHByb2R1Y2VuIG1heW9yIGFwYWxhbmNhbWllbnRvKGxldmVyYWdlKSB5IHBvciBlbmRlCnNlc2dvIGVuIGFsIHByZWRpY2Npw7NuIGRlbCBtb2RlbG86CgpgYGB7cn0KYXVnbWVudChtb2RlbF8xKSAlPiUKICBmaWx0ZXIoLmhhdD4wLjAwMjQ1KSAlPiUKICBhcnJhbmdlKC5oYXQpCmBgYAoKIyMgNi4gTW9kZWxvIFJvYnVzdG8KCkxlZXIgZWwgYXJjaGl2byAiZW5jdWVzdGFfc2FsdWRfbW9kZWxvNi5jc3YiLiBFc3RlIMO6bHRpbW8gY29uc2lzdGUgZW4gZWwgZGF0YXNldCBvcmlnaW5hbCBkZSB0cmFpbgpjb24gbGEgaW5jb3Jwb3JhY2nDs24gZGUgYWxndW5hcyBvYnNlcnZhY2lvbmVzIGFkaWNpb25hbGVzIHF1ZSBwdWVkZW4gaW5jbHVpciB2YWxvcmVzIGF0w61waWNvcy4gRW4KcGFydGljdWxhciwgb2JzZXJ2YXIgbGEgcmVsYWNpw7NuIGVudHJlIHBlc28geSBhbHR1cmEgwr9RdcOpIG9jdXJyZSBjb24gZXN0b3MgbnVldm9zIGRhdG9zPyBFbnRyZW5hciBlbAptb2RlbG8gaW5pY2lhbCBjb24gZXN0b3MgbnVldm9zIGRhdG9zIHkgY29tZW50YXIgcXXDqSBzZSBvYnNlcnZhIGVuIGxvcyBjb2VmaWNpZW50ZXMgZXN0aW1hZG9zIHkgbGFzCm3DqXRyaWNhcyBkZSBldmFsdWFjacOzbiAoUiBjdWFkcmFkbyBhanVzdGFkbywgUk1TRSB5IE1BRSkgcmVzcGVjdG8gYWwgbW9kZWxvIGVudHJlbmFkbyBjb24gZWwgc2V0IGRlCmVudHJlbmFtaWVudG8gb3JpZ2luYWwuIEVudHJlbmFyIHVuIG1vZGVsbyByb2J1c3RvIGNvbiBsYSBtaXNtYSBlc3BlY2lmaWNhY2nDs24gcXVlIGVsIG1vZGVsbyBpbmljaWFsCnNvYnJlIGxvcyBudWV2b3MgZGF0b3MuIENvbXBhcmFyIGxvcyBjb2VmaWNpZW50ZXMgeSBzdSBwZXJmb3JtYW5jZSAoUk1TRSB5IE1BRSkgcmVzcGVjdG8gYWwgbW9kZWxvCmluaWNpYWwgbm8gcm9idXN0byBlbnRyZW5hZG8gZW4gZXN0ZSBwdW50by4gwr9RdcOpIHB1ZWRlIGNvbmNsdWlyIGFsIHJlc3BlY3RvPwoKU2UgY2FyZ2EgZWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50byBlbiBjcnVkbyxlIHMgZGVjaXIgc2luIHByZS1wcm9jZXNhbWllbnRvLiBMdWVnbyBzZSByZXN1bWVuIGxvcwp2YWxvcmVzIGRlIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIHkgc2UgZWxpbWluYW4gbWlzc2luZyB2YWx1ZXMsIHlhIHF1ZSBzaWd1ZW4gc2llbmRvIG11eSBwb2NvCmNhc29zOgoKYGBge3Igd2FybmluZz1GQUxTRX0Kb3JpZ2luYWxfdHJhaW5fc2V0IDwtIHNob3J0ZW5fdmFsdWVzKHByZXByb2Nlc3MobG9hZF9vcmlnaW5hbF90cmFpbl9zZXQoKSkpCm1pc3NpbmdzX3N1bW1hcnkob3JpZ2luYWxfdHJhaW5fc2V0KQpuZXdfdHJhaW5fc2V0IDwtIGRyb3BfbWlzc2luZ3Mob3JpZ2luYWxfdHJhaW5fc2V0KQptaXNzaW5nc19zdW1tYXJ5KG5ld190cmFpbl9zZXQpCmBgYAoKYGBge3J9Cm5yb3cob3JpZ2luYWxfdHJhaW5fc2V0KQpucm93KG5ld190cmFpbl9zZXQpCmBgYAoKQ29tcGFyZW1vcyBsYXMgZGlzdHJpYnVjaW9uZXMgZGVsIHBlc28gdnMgYWx0dXJhIGVuIGFtYm9zIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG86CgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0zLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcid9CmJveF9wbG90cygKICB0cmFpbl9zZXQgJT4lIHNlbGVjdChwZXNvLCBhbHR1cmEpLCAKICB0aXRsZSA9ICdDb21wYXJhdGl2YXMgZGUgZGlzdHJpYnVjaW9uZXMgZGVsIHBlc28geSBsYSBhbHR1cmEnCikKCmJveF9wbG90cygKICBuZXdfdHJhaW5fc2V0ICU+JSBzZWxlY3QocGVzbywgYWx0dXJhKSwgCiAgdGl0bGUgPSAnQ29tcGFyYXRpdmFzIGRlIGRpc3RyaWJ1Y2lvbmVzIGRlbCBwZXNvIHkgbGEgYWx0dXJhJwopCmBgYAoKRW4gZWwgZGF0YXNldCBkZSBlbnRyZW5hbWllbnRvIG9yaWdpbmFsIGxhIHZhcmlhYmxlIHBlc28gdGllbmUgcHLDoWN0aWNhbWVudGUgZWwgZG9ibGUgZGUgb3V0bGllcnMKcXVlIGVsIGRhdGFzZXQgcHJvY2VzYWRvLgoKKipNb2RlbG8gNioqCgpEZWZpbmltb3MgdW4gbW9kZWxvIGlndWFsIGFsICoqbW9kZWxvIDEqKiBwZXJvIGVudHJlbmFuZG8gZW4gZWwgZGF0YXNldCBkZSBlbnRyZW5hbWllbnRvIG9yaWdpbmFsLgoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9Niwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInfQptb2RlbF82IDwtIGxtKAogIHBlc28gfiBhbHR1cmEgKyBlZGFkICsgZGlhc19hY3RpdmlkYWRfZmlzaWNhX3NlbWFuYWwgKyBjb25zdW1vX2RpYXJpb19hbGNvaG9sLCAKICBkYXRhID0gbmV3X3RyYWluX3NldAopCgpjb2VmZmljaWVudHNfc3VtbWFyeShtb2RlbF82KQphbm92YV9zdW1tYXJ5KG1vZGVsXzYpCmdsYW5jZShtb2RlbF82KQpgYGAKCmBgYHtyfQpwcmludChwYXN0ZSgnRGlzbWluaWNpb24gZGUgYWRqLnIuc3F1YXJlZDonLCBhYnMoMC4zNTIxMTMgLSAwLjI3MzQ4MjEpICogMTAwLCAnJScpKQpgYGAKCkRhZGEgbGEgcHJlc2VuY2lhIGRlIG91dGxpZXJzIGVuIGxhIHZhcmlhYmxlIHBlc28sIGVsICRSXjIkIEFqdXN0YWRvIGJhamEgY29uIHJlc3BlY3RvIGFsICoqbW9kZWxvCjEqKi4KCmBgYHtyfQptb2RlbHMgPC0gbGlzdCgnTW9kZWxvIDYnPW1vZGVsXzYpCgptb2RlbHNfZXZhbHVhdGlvbl9zdW1tYXJ5KG1vZGVscywgdHJhaW5fc2V0LCBtZXRyaWNfZm4gPSBybXNlKQptb2RlbHNfZXZhbHVhdGlvbl9zdW1tYXJ5KG1vZGVscywgdHJhaW5fc2V0LCBtZXRyaWNfZm4gPSBtYWUpCmBgYAoKUG9yIG90cm8gbGFkbywgYXVtZW50byBlbCBlcnJvciBkZSBwcmVkaWNjacOzbiB0YW50byBlbiB0cmFpbiBjb21vIGVuIHRlc3QuIEZpbmFsbWVudGUsIGVsIG1vZGVsbwp0aWVuZSB1biBncmFkbyBkZSBvdmVyZml0dGluZyBtdWNobyBtYXlvciBxdWUgbG9zIG1vZGVsb3MgYW50ZXJpb3JlcywgeWEgcXVlIGxhIG3DqXRyaWNhIGRlCmV2YWx1YWNpw7NuIGVuIHRlc3QgeSB0cmFpbiB0aWVuZSB1bmEgZGlmZXJlbmNpYSBtdXkgcHJvbnVuY2lhZGEgZGUgMS43IHB1bnRvcy4KCioqTW9kZWxvIDcqKgoKRGVmaW5pbW9zIHVuIG1vZGVsbyBpZ3VhbCBhbCAqKm1vZGVsbyAxKiogZW50cmVuYW5kbyBlbiBlbCBkYXRhc2V0IGRlIGVudHJlbmFtaWVudG8gb3JpZ2luYWwgeQp1c2Ftb3MgdW4gbW9kZWxvIGxpbmVhbCByb2J1c3RvLgoKYGBge3J9CnBfbG9hZChNQVNTKQoKbW9kZWxfNyA8LSBybG0oCiAgcGVzbyB+IGFsdHVyYSArIGVkYWQgKyBkaWFzX2FjdGl2aWRhZF9maXNpY2Ffc2VtYW5hbCArIGNvbnN1bW9fZGlhcmlvX2FsY29ob2wsIAogIGRhdGEgPSBuZXdfdHJhaW5fc2V0CikKY29lZmZpY2llbnRzX3N1bW1hcnkobW9kZWxfNykKYW5vdmFfc3VtbWFyeShtb2RlbF83KQpgYGAKCmBgYHtyfQptb2RlbHMgPC0gbGlzdCgnTW9kZWxvIDYnPW1vZGVsXzYsICdNb2RlbG8gNyc9bW9kZWxfNykKCm1vZGVsc19ldmFsdWF0aW9uX3N1bW1hcnkobW9kZWxzLCB0ZXN0X3NldCwgbWV0cmljX2ZuID0gcm1zZSkKbW9kZWxzX2V2YWx1YXRpb25fc3VtbWFyeShtb2RlbHMsIHRlc3Rfc2V0LCBtZXRyaWNfZm4gPSBtYWUpCmBgYAoKRWwgbW9kZWxvIGxpbmVhbCByb2J1c3RvIChNb2RlbG8gNykgcGFyZWNlIHRlbmVyIHVuIG1lbm9yIGVycm9yIGRlIGVudHJlbmFtaWVudG8gbXV5IGNlcmNhbm8gYWwKbW9kZWxvIDYsIHBlcm8gdGllbmUgbWF5b3Igc29icmUtIGFqdXN0ZSBxdWUgZWwgbW9kZWxvIDYsIGF1bnF1ZSBlcyB1bmEgZGlmZXJlbmNpYSBtdXkgYmFqYS4KCkRhZG8gZXN0bywgc2VyaWEgdW5hIGJ1ZW5hIHNlbGVjY2lvbm8gZWxlZ2lyIGVsIG1vZGVsbyA3LCB5YSBxdWUgZWwgc29icmUgYWp1c3RlIHByYWN0aWNhbWVudGUgbm8KY2FtYmlhIHkgb2J0ZW5lbW9zIHVuIGVycm9yIGRlIHByZWRpY2Npw7NuIGVuIHRlc3QgbGlnZXJhbWVudGUgbWVub3IuCg==